package configkit_test import ( "bytes" "io/fs" "reflect" "strings" "testing" "testing/fstest" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) type inheritedEntry struct { Enabled bool `yaml:"enabled"` Count int `yaml:"count"` Resource string `yaml:"resource"` Settings map[string]map[string]string `yaml:"settings"` } type inheritedConfiguration struct { Version string `yaml:"version"` Entries map[string]inheritedEntry `yaml:"entries"` } func TestFacadeInheritancePreservesPresenceOriginsAndHistory(t *testing.T) { input := []byte(`entries: parent: enabled: false count: 0 settings: primary: {value: original} secondary: {extends: primary} child: extends: parent settings: primary: {value: custom} `) defaultRan := false result, err := configkit.Load(configkit.Bytes("config.yml", input), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{ Resolve: func(config *inheritedConfiguration, resolution *configkit.Resolution) error { if !resolution.IsExplicit(configkit.Keys("entries", "child", "enabled")) { t.Error("inherited false is not explicit") } info, found := resolution.InputInfo(configkit.Keys("entries", "child", "enabled")) if !found || len(info.Origin.Inheritance) != 1 { t.Errorf("resolver inherited origin = %#v, %v", info, found) } child := config.Entries["child"] configkit.Default(resolution, configkit.Keys("entries", "child", "enabled"), &child.Enabled, true, "use fallback") defaultRan = child.Enabled config.Entries["child"] = child return nil }, }}) if err != nil { t.Fatal(err) } child := result.Config.Entries["child"] if defaultRan || child.Enabled || child.Count != 0 || child.Settings["secondary"]["value"] != "custom" { t.Fatalf("inherited values = %#v, default ran %v", child, defaultRan) } path := configkit.Keys("entries", "child", "settings", "secondary", "value") info, found := result.Metadata().Lookup(path) if !found || !info.Explicit || len(info.Origin.Inheritance) != 1 || info.Origin.Inheritance[0].From.String() != "/entries/child/settings/primary/value" || info.Origin.Inheritance[0].To.String() != path.String() { t.Fatalf("nested destination origin = %#v, %v", info, found) } info.Origin.Inheritance[0].Locations[0].SourceID = "mutated" info, _ = result.Metadata().Lookup(path) if info.Origin.Inheritance[0].Locations[0].SourceID == "mutated" { t.Fatal("public inheritance record aliases inspection state") } if _, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "extends")); found { t.Fatal("consumed directive remained effective") } control, found := result.Explain(configkit.Keys("entries", "child", "extends")) if !found || control.Effective.Present || len(control.History) < 2 || control.History[len(control.History)-1].Origin.Operation != configkit.ChangeInheritance { t.Fatalf("consumed directive = %#v, %v", control, found) } explanation, found := result.Explain(path) if !found || explanation.Effective.YAML != "custom\n" { t.Fatalf("nested explanation = %#v, %v", explanation, found) } for _, change := range explanation.History { if change.Origin.Operation == configkit.ChangeDecoding { t.Fatalf("inherited value attributed to decoding: %#v", explanation.History) } } } func TestFacadeInheritanceLayeredPrecedenceAndSourceContext(t *testing.T) { filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{ "config/main.yml": &fstest.MapFile{Data: []byte("entries: {child: {extends: parent}}\n")}, "shared/parent.yml": &fstest.MapFile{Data: []byte("entries: {parent: {resource: icon.txt, count: 1}}\n")}, "shared/flags.yml": &fstest.MapFile{Data: []byte("entries: {parent: {enabled: false}}\n")}, "shared/icon.txt": &fstest.MapFile{Data: []byte("parent-resource")}, "config/overlays/test.yml": &fstest.MapFile{Data: []byte("entries: {parent: {count: 2}}\n")}, }) result, err := configkit.LoadLayered(configkit.Input{Source: filesystem.File("config/main.yml")}, configkit.LayeredOptions[inheritedConfiguration]{ Underlays: []configkit.Underlay{ {Inputs: []configkit.Input{{Source: filesystem.File("shared/parent.yml")}}}, {Inputs: []configkit.Input{{Source: filesystem.File("shared/flags.yml")}}}, }, Overlay: "test", OverlayFilesystem: &configkit.FilesystemContext{Root: "config", BaseDir: "config", FS: &filesystem}, Overrides: []configkit.Override{{Path: configkit.Keys("entries", "child", "count"), Value: 3, Flag: "--count"}}, }) if err != nil { t.Fatal(err) } child := result.Config.Entries["child"] if child.Count != 3 || child.Resource != "icon.txt" { t.Fatalf("layered inheritance = %#v", child) } info, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "resource")) if !found || len(info.Origin.Inheritance) != 1 || len(info.Origin.Locations) != 1 { t.Fatalf("resource origin = %#v, %v", info, found) } context, found := result.SourceContext(info.Origin.Locations[0].SourceID) if !found || context.BaseDir != "shared" || context.FS == nil { t.Fatalf("source context = %#v, %v", context, found) } data, err := fs.ReadFile(context.FS, context.BaseDir+"/"+child.Resource) if err != nil || string(data) != "parent-resource" { t.Fatalf("source-relative resource = %q, %v", data, err) } count, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "count")) if !found || count.Origin.Layer != configkit.LayerCLI || len(count.Origin.Inheritance) != 0 { t.Fatalf("CLI replacement origin = %#v, %v", count, found) } var rendered bytes.Buffer if err := result.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), "extends:") { t.Fatalf("effective rendering = %q, %v", rendered.String(), err) } } func TestFacadeInheritanceMappingMatchesBytes(t *testing.T) { values := map[string]any{"entries": map[string]any{ "parent": map[string]any{"enabled": false, "count": 0}, "child": map[string]any{"extends": "parent"}, }} mapped, err := configkit.Load[inheritedConfiguration](configkit.Mapping("mapping", values)) if err != nil { t.Fatal(err) } fromYAML, err := configkit.Load[inheritedConfiguration](configkit.Bytes("config.yml", []byte("entries: {parent: {enabled: false, count: 0}, child: {extends: parent}}\n"))) if err != nil || !reflect.DeepEqual(mapped.Config, fromYAML.Config) { t.Fatalf("mapping and bytes differ: %#v, %#v, %v", mapped, fromYAML, err) } } func TestFacadeInheritanceProtectsVersionOwnership(t *testing.T) { for _, test := range []struct { name, input string path configkit.Path wantFailure bool }{ {"inherited version", "entries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("entries", "child", "count"), true}, {"overridden version", "entries: {parent: {count: 1}, child: {extends: parent, count: 1}}\n", configkit.Keys("entries", "child", "count"), true}, {"direct unchanged version", "entries: {parent: {enabled: false}, child: {extends: parent, count: 1}}\n", configkit.Keys("entries", "child", "count"), false}, {"consumed checked directive", "entries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("entries", "child", "extends"), true}, {"independent top-level version", "version: v1\nentries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("version"), false}, } { t.Run(test.name, func(t *testing.T) { checks := struct{ version, defaults, resolution, validation int }{} result, err := configkit.Load(configkit.Bytes("version.yml", []byte(test.input)), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{ Version: &configkit.VersionRule{Path: test.path, Check: func(configkit.Version, *configkit.Validation) error { checks.version++ return nil }}, Defaults: func() inheritedConfiguration { checks.defaults++; return inheritedConfiguration{} }, Resolve: func(*inheritedConfiguration, *configkit.Resolution) error { checks.resolution++; return nil }, Validate: func(*inheritedConfiguration, *configkit.Validation) { checks.validation++ }, }}) if test.wantFailure { if result != nil || err == nil { t.Fatalf("protected version = %#v, %v", result, err) } loadError, ok := err.(*configkit.LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeInheritanceVersion || checks != (struct{ version, defaults, resolution, validation int }{version: 1}) { t.Fatalf("version failure = %v, callbacks %#v", err, checks) } } else if err != nil || result == nil || checks != (struct{ version, defaults, resolution, validation int }{1, 1, 1, 1}) { t.Fatalf("unchanged version = %#v, %v, callbacks %#v", result, err, checks) } }) } } func TestFacadeInheritanceFailureStopsLaterCallbacks(t *testing.T) { checks := struct{ defaults, resolution, validation int }{} result, err := configkit.Load(configkit.Bytes("invalid.yml", []byte("entries: {child: {extends: PRIVATE-REFERENCE-CANARY}}\n")), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{ Defaults: func() inheritedConfiguration { checks.defaults++; return inheritedConfiguration{} }, Resolve: func(*inheritedConfiguration, *configkit.Resolution) error { checks.resolution++; return nil }, Validate: func(*inheritedConfiguration, *configkit.Validation) { checks.validation++ }, }}) if result != nil || err == nil || checks != (struct{ defaults, resolution, validation int }{}) || strings.Contains(err.Error(), "PRIVATE-REFERENCE-CANARY") { t.Fatalf("invalid inheritance = %#v, %v, callbacks %#v", result, err, checks) } loadError, ok := err.(*configkit.LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeInheritanceMissing { t.Fatalf("missing reference diagnostics = %v", err) } } func TestFacadeInheritanceResolvesAfterImportsAndCLIReferenceChange(t *testing.T) { filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: parent.yml}]}\nentries: {child: {extends: first}}\n")}, "parent.yml": &fstest.MapFile{Data: []byte("entries: {first: {count: 1}, second: {count: 2}}\n")}, }) result, err := configkit.Load(filesystem.File("main.yml"), configkit.Options[inheritedConfiguration]{ Imports: true, Overrides: []configkit.Override{{Path: configkit.Keys("entries", "child", "extends"), Value: "second", Flag: "--extends"}}, }) if err != nil || result.Config.Entries["child"].Count != 2 { t.Fatalf("import and CLI reference = %#v, %v", result, err) } history := result.Metadata().History(configkit.Keys("entries", "child", "extends")) if len(history) < 3 || history[len(history)-1].Origin.Operation != configkit.ChangeInheritance { t.Fatalf("superseded reference history = %#v", history) } conflicting := configkit.NewFilesystem("bundle", fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: parent.yml}]}\nentries: {child: {extends: first}, first: {count: 3}}\n")}, "parent.yml": &fstest.MapFile{Data: []byte("entries: {first: {count: 1}}\n")}, }) _, err = configkit.Load[inheritedConfiguration](conflicting.File("main.yml"), configkit.Options[inheritedConfiguration]{Imports: true}) loadError, ok := err.(*configkit.LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeComposeConflict { t.Fatalf("strict conflict before expansion = %v", err) } } func TestFacadeInheritanceSelectedOverlayCancelsRelationship(t *testing.T) { filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("entries: {parent: {count: 7}, child: {extends: parent, enabled: false}}\n")}, "overlays/test.yml": &fstest.MapFile{Data: []byte("entries: {child: {extends: null}}\n")}, }) result, err := configkit.Load(filesystem.File("main.yml"), configkit.Options[inheritedConfiguration]{ Overlay: "test", Filesystem: &configkit.FilesystemContext{Root: ".", FS: &filesystem}, }) if err != nil || result.Config.Entries["child"].Count != 0 || result.Config.Entries["child"].Enabled { if loadError, ok := err.(*configkit.LoadError); ok { t.Fatalf("cancelled relationship = %#v, %v, diagnostics %#v", result, err, loadError.Diagnostics()) } t.Fatalf("cancelled relationship = %#v, %v", result, err) } history := result.Metadata().History(configkit.Keys("entries", "child", "extends")) if len(history) < 3 || history[len(history)-1].Origin.Operation != configkit.ChangeInheritance { t.Fatalf("cancellation history = %#v", history) } } func TestFacadeInheritedSecretsStayRedactedAfterLookup(t *testing.T) { const canary = "INHERITED-RELEASE-TEST-CREDENTIAL" t.Setenv("CONFIGKIT_INHERITED_TEST_SECRET", canary) type entry struct { Credential configkit.Secret `yaml:"credential"` Plain string `yaml:"plain"` } type configuration struct { Entries map[string]entry `yaml:"entries"` } result, err := configkit.Load[configuration](configkit.Bytes("secret.yml", []byte("entries: {parent: {credential: CONFIGKIT_INHERITED_TEST_SECRET, plain: visible}, child: {extends: parent}}\n")), configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ Secret: func(path configkit.Path) bool { return path.String() == "/entries/parent/credential" }, }}) if err != nil || result.Config.Entries["child"].Credential.Value() != canary { t.Fatalf("inherited credential = %#v, %v", result, err) } path := configkit.Keys("entries", "child", "credential") explanation, found := result.Explain(path) if !found || !explanation.Effective.Redacted || strings.Contains(explanation.Effective.YAML, canary) { t.Fatalf("credential explanation = %#v, %v", explanation, found) } var rendered bytes.Buffer if err := result.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), canary) || !strings.Contains(rendered.String(), "visible") { t.Fatalf("effective secret rendering = %q, %v", rendered.String(), err) } rendered.Reset() if err := result.WriteExplanation(&rendered, path); err != nil || strings.Contains(rendered.String(), canary) { t.Fatalf("historical secret rendering = %q, %v", rendered.String(), err) } }