package engine import ( "bytes" "fmt" "reflect" "strings" "testing" ) func TestRelativeImportsFeedFinalInheritanceAndInspectionLifecycle(t *testing.T) { t.Parallel() type entry struct { Extends string `yaml:"extends"` Label string `yaml:"label"` Token *Secret `yaml:"token" configkit:"secret"` Dynamic string `yaml:"dynamic"` Derived string `yaml:"derived"` Height int `yaml:"height"` } type configuration struct { Catalog struct { Entries map[string]entry `yaml:"entries"` } `yaml:"catalog"` } load := func(dynamic string) *testResult[configuration] { t.Helper() directory := t.TempDir() mainPath := writeImportFile(t, directory, "entry.yml", `configkit: imports: - path: parent.yml destination: [entries] - path: child.yml destination: [entries] `) writeImportFile(t, directory, "parent.yml", `parent: label: lower token: PARENT_TOKEN height: 1 `) writeImportFile(t, directory, "child.yml", fmt.Sprintf(`child: extends: parent label: child dynamic: %s height: 9 `, dynamic)) var defaultsCalls, resolveCalls, validateCalls int secretPaths := make(map[string]bool) lookupCalls := make(map[string]int) request := loadRequest[configuration]{ main: loadInput{ source: Bytes("main", []byte("parent: {label: final, height: 4}\n")), destination: Keys("catalog", "entries"), }, underlays: []loadGroup{{inputs: []loadInput{{ source: File(mainPath), imports: true, destination: Keys("catalog"), }}}}, definition: Definition[configuration]{ Defaults: func() configuration { defaultsCalls++ var defaults configuration defaults.Catalog.Entries = map[string]entry{"fallback": {Label: "default"}} return defaults }, Resolve: func(config *configuration, resolution *Resolution) error { resolveCalls++ child := config.Catalog.Entries["child"] child.Derived = child.Label + ":" + child.Dynamic + ":" + fmt.Sprint(child.Height) config.Catalog.Entries["child"] = child resolution.Record(Keys("catalog", "entries", "child", "derived"), ChangeNormalization, "derive final child summary", Keys("catalog", "entries", "child", "label"), Keys("catalog", "entries", "child", "dynamic"), Keys("catalog", "entries", "child", "height")) return nil }, Validate: func(config *configuration, validation *Validation) { validateCalls++ parent := config.Catalog.Entries["parent"] child := config.Catalog.Entries["child"] if parent.Label != "final" || parent.Height != 4 || child.Label != "child" || child.Height != 9 || child.Derived != child.Label+":"+child.Dynamic+":9" || child.Token == nil || !child.Token.IsResolved() || child.Token.Value() != "parent-secret-canary" { validation.Error(Keys("catalog", "entries", "child"), "final imported configuration was not available to validation") } }, Secret: func(path Path) bool { secretPaths[path.String()] = true return path.String() == "/catalog/entries/child/dynamic" }, }, } loaded, err := loadGroupedWithSecretLookup(request, testSecretBinding(), func(name string) (string, bool) { lookupCalls[name]++ if name == "PARENT_TOKEN" { return "parent-secret-canary", true } return "", false }) if err != nil { if loadError, ok := err.(*LoadError); ok { t.Fatalf("relative import lifecycle diagnostics: %#v", loadError.Diagnostics()) } t.Fatalf("relative import lifecycle: %v", err) } if defaultsCalls != 1 || resolveCalls != 1 || validateCalls != 1 || lookupCalls["PARENT_TOKEN"] != 1 { t.Fatalf("lifecycle calls defaults=%d resolve=%d validate=%d lookups=%#v", defaultsCalls, resolveCalls, validateCalls, lookupCalls) } if !secretPaths["/catalog/entries/child/dynamic"] { t.Fatalf("secret classifier did not see effective paths: %#v", secretPaths) } return testLoaded(loaded) } left := load("LEFT-DYNAMIC-CANARY") child := left.Config.Catalog.Entries["child"] if child.Extends != "" || child.Label != "child" || child.Height != 9 || child.Derived != "child:LEFT-DYNAMIC-CANARY:9" || child.Token == nil || child.Token.Value() != "parent-secret-canary" { t.Fatalf("resolved imported child = %#v", child) } if left.Config.Catalog.Entries["parent"].Label != "final" || left.Config.Catalog.Entries["parent"].Height != 4 || left.Config.Catalog.Entries["fallback"].Label != "default" { t.Fatalf("final parent/default shape = %#v", left.Config.Catalog.Entries) } childTokenPath := Keys("catalog", "entries", "child", "token") childToken, found := left.Inspection.Explain(childTokenPath) childTokenInfo, tokenInfoFound := left.Inspection.Metadata().Lookup(childTokenPath) if !found || !tokenInfoFound || !childToken.Effective.Redacted || strings.Contains(childToken.Effective.YAML, "parent-secret-canary") || len(childTokenInfo.Origin.Inheritance) != 1 { t.Fatalf("inherited field explanation = %#v, %v", childToken, found) } var explanation bytes.Buffer if err := left.Inspection.WriteExplanation(&explanation, childTokenPath); err != nil || strings.Contains(explanation.String(), "parent-secret-canary") || strings.Contains(explanation.String(), "LEFT-DYNAMIC-CANARY") { t.Fatalf("safe explanation = %q, %v", explanation.String(), err) } labelHistory := left.Inspection.Metadata().History(Keys("catalog", "entries", "parent", "label")) if len(labelHistory) < 2 { t.Fatalf("parent label history omitted replaced contribution: %#v", labelHistory) } derived, found := left.Inspection.Explain(Keys("catalog", "entries", "child", "derived")) if !found || !derived.Effective.Redacted || strings.Contains(derived.Effective.YAML, "LEFT-DYNAMIC-CANARY") || len(derived.History) == 0 || derived.History[len(derived.History)-1].Origin.Operation != ChangeNormalization { t.Fatalf("recorded derived field explanation = %#v, %v", derived, found) } right := load("RIGHT-DYNAMIC-CANARY") differences, err := Diff(left.Inspection, right.Inspection) if err != nil { t.Fatal(err) } seen := make(map[string]bool) for _, difference := range differences { path := difference.Path.String() seen[path] = true if !difference.Left.Redacted || !difference.Right.Redacted || !difference.HiddenChanged || strings.Contains(difference.Left.YAML+difference.Right.YAML, "DYNAMIC-CANARY") || strings.Contains(difference.Left.YAML+difference.Right.YAML, "parent-secret-canary") { t.Errorf("sensitive difference at %s = %#v", path, difference) } } for _, path := range []string{"/catalog/entries/child/dynamic", "/catalog/entries/child/derived"} { if !seen[path] { t.Errorf("missing inherited/derived difference at %s: %#v", path, differences) } } } func TestRelativeImportMatchesWrappedPlacementAndSafeDiff(t *testing.T) { t.Parallel() load := func(relativeImport bool, value string) *testResult[map[string]any] { t.Helper() input := Input{Source: NoSource()} options := LayeredOptions[map[string]any]{} if relativeImport { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: fragment.yml destination: [application] `) writeImportFile(t, directory, "fragment.yml", "field: "+value+"\n") options.Underlays = []Underlay{{Inputs: []Input{{Source: File(mainPath), Imports: true}}}} } else { options.Underlays = []Underlay{{Inputs: []Input{{Source: Bytes("wrapped", []byte("application: {field: "+value+"}\n"))}}}} } loaded, err := LoadLayered(input, testSecretBinding(), options) if err != nil { t.Fatalf("LoadLayered: %v", err) } return testLoaded(loaded) } relativeBefore := load(true, "before") relativeAfter := load(true, "after") wrappedBefore := load(false, "before") wrappedAfter := load(false, "after") for _, pair := range [][2]*testResult[map[string]any]{{relativeBefore, wrappedBefore}, {relativeAfter, wrappedAfter}} { if !reflect.DeepEqual(pair[0].Config, pair[1].Config) { t.Fatalf("relative and wrapped configurations differ: %#v vs %#v", pair[0].Config, pair[1].Config) } for _, result := range pair { if info, ok := result.Metadata().Lookup(Keys("application", "field")); !ok || !info.Explicit { t.Fatalf("effective field presence = %#v, %v", info, ok) } } } relativeDiff, err := Diff(relativeBefore.Inspection, relativeAfter.Inspection) if err != nil { t.Fatal(err) } wrappedDiff, err := Diff(wrappedBefore.Inspection, wrappedAfter.Inspection) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(relativeDiff, wrappedDiff) { t.Fatalf("relative safe diff = %#v, wrapped safe diff = %#v", relativeDiff, wrappedDiff) } }