package engine import ( "path/filepath" "testing" "testing/fstest" ) type importDestinationConfig struct { Mounted map[string]string `yaml:"mounted"` Collections map[string]map[string]string `yaml:"collections"` } func TestLayeredHostImportsInheritInputDestinationTransitively(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: first.yml - path: second.yml root: main `) firstPath := writeImportFile(t, directory, "first.yml", `configkit: imports: - path: nested/third.yml first: one `) thirdPath := writeImportFile(t, directory, "nested/third.yml", "nested: three\n") secondPath := writeImportFile(t, directory, "second.yml", "second: two\n") loaded, err := LoadLayered( Input{Source: File(mainPath), Imports: true, Destination: Keys("mounted")}, testSecretBinding(), LayeredOptions[importDestinationConfig]{}, ) if err != nil { if loadError, ok := err.(*LoadError); ok { t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics()) } t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) want := map[string]string{"root": "main", "first": "one", "nested": "three", "second": "two"} if len(result.Config.Mounted) != len(want) { t.Fatalf("mounted values = %#v", result.Config.Mounted) } for key, value := range want { if result.Config.Mounted[key] != value { t.Errorf("mounted[%q] = %q, want %q", key, result.Config.Mounted[key], value) } } if _, ok := result.Metadata().Lookup(Keys("mounted", "configkit")); ok { t.Fatal("source-root import directive leaked into application content") } sources := result.Sources() wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, firstPath), canonicalTestPath(t, thirdPath), canonicalTestPath(t, secondPath)} if len(sources) != len(wantPaths) { t.Fatalf("sources = %#v", sources) } for index, source := range sources { if source.Destination.String() != "/mounted" { t.Errorf("source %d destination = %q", index, source.Destination.String()) } if source.Path != wantPaths[index] { t.Errorf("source %d path = %q, want %q", index, source.Path, wantPaths[index]) } if index > 0 && source.ImportedFrom == nil { t.Errorf("imported source %d has no physical import location", index) } } } func TestImportTraversalUsesPerDeclarationDestinations(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: parent.yml - path: sibling.yml - glob: fragments/*.yml `) parentPath := writeImportFile(t, directory, "parent.yml", `configkit: imports: - path: nested/child.yml parent: true `) childPath := writeImportFile(t, directory, "nested/child.yml", "child: true\n") siblingPath := writeImportFile(t, directory, "sibling.yml", "sibling: true\n") alphaPath := writeImportFile(t, directory, "fragments/a.yml", "alpha: true\n") betaPath := writeImportFile(t, directory, "fragments/b.yml", "beta: true\n") main := parseImportMain(t, mainPath) _, declarations, diagnostics := splitImportDirectives(main.document) if len(diagnostics) != 0 { t.Fatalf("main directive diagnostics = %#v", diagnostics) } declarations[0].destination = Keys("parent") declarations[1].destination = Keys("sibling") declarations[2].destination = Keys("collection") access, err := openHostAccess(directory) if err != nil { t.Fatal(err) } defer func() { _ = access.close() }() state := importTraversal{ access: access, accounting: &importAccounting{}, limits: defaultImportLimits(), active: make(map[resourceIdentity]int), visited: make(map[resourceIdentity]importRoute), } if diagnostics := state.walkDeclarations(directory, declarations, 0, Keys("entry")); len(diagnostics) != 0 { t.Fatalf("walk diagnostics = %#v", diagnostics) } wantSources := []struct { path string destination string }{ {path: parentPath, destination: "/entry/parent"}, {path: childPath, destination: "/entry/parent"}, {path: siblingPath, destination: "/entry/sibling"}, {path: alphaPath, destination: "/entry/collection"}, {path: betaPath, destination: "/entry/collection"}, } if len(state.sources) != len(wantSources) { t.Fatalf("import sources = %#v, want %d", state.sources, len(wantSources)) } for index, want := range wantSources { got := state.sources[index] if got.Path != canonicalTestPath(t, want.path) || got.Destination.String() != want.destination { t.Errorf("source %d = (%q, %q), want (%q, %q)", index, got.Path, got.Destination.String(), canonicalTestPath(t, want.path), want.destination) } } composed, diagnostics := composeDocuments(state.documents...) if len(diagnostics) != 0 { t.Fatalf("composition diagnostics = %#v", diagnostics) } for _, path := range []Path{ Keys("entry", "parent", "parent"), Keys("entry", "parent", "child"), Keys("entry", "sibling", "sibling"), Keys("entry", "collection", "alpha"), Keys("entry", "collection", "beta"), } { if _, present := documentAtPath(composed.root, path); !present { t.Errorf("no composed value at %q", path.String()) } } } func TestEntryDestinationSurvivesImportSetupAndParseFailures(t *testing.T) { t.Parallel() t.Run("import context failure", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n") input, diagnostics := assembleBaseInputTracked( File(mainPath), Options[map[string]any]{Imports: true, Filesystem: &FilesystemContext{Root: t.TempDir()}}, Keys("mounted"), &importAccounting{}, nil, nil, ) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot { t.Fatalf("context diagnostics = %#v", diagnostics) } if len(input.sources) != 1 || input.sources[0].Destination.String() != "/mounted" { t.Fatalf("entry source fallback = %#v", input.sources) } }) t.Run("imported parse failure", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: broken.yml}]}\n") writeImportFile(t, directory, "broken.yml", "broken: [\n") input, diagnostics := assembleBaseInputTracked( File(mainPath), Options[map[string]any]{Imports: true}, Keys("mounted"), &importAccounting{}, nil, nil, ) if len(diagnostics) != 1 || diagnostics[0].Code != CodeYAMLInvalid { t.Fatalf("parse diagnostics = %#v", diagnostics) } if len(input.sources) != 2 { t.Fatalf("parse-failure sources = %#v", input.sources) } for index, source := range input.sources { if source.Destination.String() != "/mounted" { t.Errorf("source %d destination = %q, want /mounted", index, source.Destination.String()) } } }) } func TestRelativeImportDestinationsKeepStrictCompositionBoundaries(t *testing.T) { t.Parallel() for _, test := range []struct { name string main string files map[string]string want map[string]any conflict bool wantPath string }{ { name: "disjoint peers at one destination", main: "configkit:\n imports:\n - path: left.yml\n destination: [application]\n - path: right.yml\n destination: [application]\n", files: map[string]string{"left.yml": "left: one\n", "right.yml": "right: two\n"}, want: map[string]any{"/application/left": "one", "/application/right": "two"}, }, { name: "different destinations", main: "configkit:\n imports:\n - path: left.yml\n destination: [left]\n - path: right.yml\n destination: [right]\n", files: map[string]string{"left.yml": "value: shared\n", "right.yml": "value: shared\n"}, want: map[string]any{"/left/value": "shared", "/right/value": "shared"}, }, { name: "ancestor and descendant overlap", main: "configkit:\n imports:\n - path: parent.yml\n destination: [application]\n - path: child.yml\n destination: [application, branch]\n", files: map[string]string{"parent.yml": "branch: {base: one}\n", "child.yml": "leaf: two\n"}, want: map[string]any{"/application/branch/base": "one", "/application/branch/leaf": "two"}, }, { name: "collision with explicitly wrapped content", main: "configkit: {imports: [{path: fragment.yml, destination: [application]}]}\napplication: {value: root}\n", files: map[string]string{"fragment.yml": "value: imported\n"}, conflict: true, wantPath: "/application/value", }, { name: "equal peer values conflict", main: "configkit:\n imports:\n - path: left.yml\n destination: [application]\n - path: right.yml\n destination: [application]\n", files: map[string]string{"left.yml": "value: same\n", "right.yml": "value: same\n"}, conflict: true, wantPath: "/application/value", }, { name: "unequal peer values conflict", main: "configkit:\n imports:\n - path: left.yml\n destination: [application]\n - path: right.yml\n destination: [application]\n", files: map[string]string{"left.yml": "value: left\n", "right.yml": "value: right\n"}, conflict: true, wantPath: "/application/value", }, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", test.main) for name, body := range test.files { writeImportFile(t, directory, name, body) } loaded, err := LoadLayered( Input{Source: File(mainPath), Imports: true}, testSecretBinding(), LayeredOptions[map[string]any]{}, ) if test.conflict { if loaded != nil || groupedErrorCode(t, err) != CodeComposeConflict { t.Fatalf("relative import composition = %#v, %v", loaded, err) } loadError := err.(*LoadError) if diagnostics := loadError.Diagnostics(); diagnostics[0].Path.String() != test.wantPath { t.Fatalf("conflict path = %q, want %q", diagnostics[0].Path.String(), test.wantPath) } return } if err != nil { t.Fatalf("relative import composition: %v", err) } configuration := testLoaded(loaded).Config for path, want := range test.want { parsed, err := ParsePath(path) if err != nil { t.Fatal(err) } keys := make([]string, len(parsed.segments)) for index, segment := range parsed.segments { keys[index] = segment.key } if got := layeredMapValue(t, configuration, keys...); got != want { t.Errorf("value at %s = %#v, want %#v", path, got, want) } } }) } t.Run("upper tier cannot repair a lower import conflict", func(t *testing.T) { directory := t.TempDir() lowerPath := writeImportFile(t, directory, "lower.yml", `configkit: imports: - path: left.yml destination: [application] - path: right.yml destination: [application] `) writeImportFile(t, directory, "left.yml", "value: left\n") writeImportFile(t, directory, "right.yml", "value: right\n") loaded, err := LoadLayered( Input{Source: Bytes("upper", []byte("application: {value: upper}\n"))}, testSecretBinding(), LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{{Source: File(lowerPath), Imports: true}}}}}, ) if loaded != nil || groupedErrorCode(t, err) != CodeComposeConflict { t.Fatalf("upper tier repaired a lower conflict: %#v, %v", loaded, err) } }) } func TestRelativeImportDestinationsUseEffectiveVersionOwnership(t *testing.T) { t.Parallel() for _, test := range []struct { name string rulePath Path fragment string }{ {name: "direct claim", rulePath: Keys("collection", "group", "nested", "version"), fragment: "version: v2\n"}, {name: "ancestor claim", rulePath: Keys("collection", "group", "nested", "schema", "version"), fragment: "schema: null\n"}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: parent.yml destination: [group] `) writeImportFile(t, directory, "parent.yml", `configkit: imports: - path: fragment.yml destination: [nested] `) writeImportFile(t, directory, "fragment.yml", test.fragment) _, err := LoadLayered( Input{Source: File(mainPath), Imports: true, Destination: Keys("collection")}, testSecretBinding(), LayeredOptions[map[string]any]{Definition: Definition[map[string]any]{Version: &VersionRule{ Path: test.rulePath, Check: func(Version, *Validation) error { return nil }, }}}, ) if groupedErrorCode(t, err) != CodeImportVersion { t.Fatalf("effective imported claim error = %v", err) } loadError := err.(*LoadError) diagnostic := loadError.Diagnostics()[0] if diagnostic.Path.String() != test.rulePath.String() || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-3" { t.Fatalf("effective version diagnostic = %#v", diagnostic) } sources := loadError.Sources() wantDestinations := []string{"/collection", "/collection/group", "/collection/group/nested"} if len(sources) != len(wantDestinations) { t.Fatalf("version failure sources = %#v", sources) } for index, source := range sources { if source.Destination.String() != wantDestinations[index] { t.Errorf("source %d destination = %q, want %q", index, source.Destination.String(), wantDestinations[index]) } } }) } } func TestRelativeImportDestinationsKeepMainVersionCheckScoped(t *testing.T) { t.Parallel() type config struct { Collection struct { SchemaVersion string `yaml:"schema_version"` Group map[string]any `yaml:"group"` } `yaml:"collection"` } directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: parent.yml destination: [group] schema_version: 6 `) writeImportFile(t, directory, "parent.yml", `configkit: imports: - path: fragment.yml destination: [nested] `) writeImportFile(t, directory, "fragment.yml", "version: 7\n") checks := 0 loaded, err := LoadLayered( Input{Source: File(mainPath), Imports: true, Destination: Keys("collection")}, testSecretBinding(), LayeredOptions[config]{Definition: Definition[config]{Version: &VersionRule{ Path: Keys("collection", "schema_version"), Check: func(version Version, _ *Validation) error { checks++ if !version.Present || version.Text != "6" { t.Errorf("main-owned version = %#v", version) } return nil }, }}}, ) if err != nil { t.Fatalf("ordinary nested imported version was rejected: %v", err) } result := testLoaded(loaded) if checks != 1 || result.Config.Collection.SchemaVersion != "6" { t.Fatalf("main version callback/value = %d/%q", checks, result.Config.Collection.SchemaVersion) } nested, ok := result.Config.Collection.Group["nested"].(map[string]any) if !ok { t.Fatalf("nested imported payload = %#v", result.Config.Collection.Group["nested"]) } if got, ok := nested["version"].(int64); !ok || got != 7 { t.Fatalf("ordinary nested version = %#v, want integer payload 7", nested["version"]) } } func TestLayeredNativeFilesystemImportsInheritInputDestination(t *testing.T) { t.Parallel() native := fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: first.yml\nroot: main\n")}, "first.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: nested.yml}]}\nfirst: imported\n")}, "nested.yml": &fstest.MapFile{Data: []byte("nested: imported\n")}, } namespace := newNativeNamespace("bundle", native) loaded, err := LoadLayered( Input{Source: filesystemFile(namespace, "main.yml"), Imports: true, Destination: Keys("mounted")}, testSecretBinding(), LayeredOptions[importDestinationConfig]{}, ) if err != nil { if loadError, ok := err.(*LoadError); ok { t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics()) } t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) if result.Config.Mounted["root"] != "main" || result.Config.Mounted["first"] != "imported" || result.Config.Mounted["nested"] != "imported" { t.Fatalf("mounted native filesystem values = %#v", result.Config.Mounted) } sources := result.Sources() if len(sources) != 3 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" || sources[2].Destination.String() != "/mounted" { t.Fatalf("native source destinations = %#v", sources) } } func TestLayeredImportOnlyInputMaterializesDestination(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: optional.yml optional: true `) loaded, err := LoadLayered( Input{Source: File(mainPath), Imports: true, Destination: Keys("collections", "empty")}, testSecretBinding(), LayeredOptions[importDestinationConfig]{}, ) if err != nil { t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) info, ok := result.Metadata().Lookup(Keys("collections", "empty")) if !ok || !info.Explicit { t.Fatalf("import-only body destination = %#v, %v", info, ok) } sources := result.Sources() if len(sources) != 1 || sources[0].Destination.String() != "/collections/empty" { t.Fatalf("sources = %#v", sources) } } func TestLayeredImportedVersionClaimsUseEffectiveDestination(t *testing.T) { t.Parallel() for _, test := range []struct { name string rulePath Path destination Path wantCode Code }{ {name: "protected effective path", rulePath: Keys("mounted", "version"), destination: Keys("mounted"), wantCode: CodeImportVersion}, {name: "ordinary nested domain version", rulePath: Keys("version"), destination: Keys("collections", "entry")}, } { 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", "version: v2\n") rule := &VersionRule{Path: test.rulePath, Check: func(Version, *Validation) error { return nil }} loaded, err := LoadLayered( Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[importDestinationConfig]{ Definition: Definition[importDestinationConfig]{Version: rule}, Underlays: []Underlay{{Inputs: []Input{{ Source: File(filepath.Join(directory, "main.yml")), Imports: true, Destination: test.destination, }}}}, }, ) if test.wantCode != "" { if err == nil { t.Fatal("protected imported version claim was accepted") } loadError, ok := err.(*LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != test.wantCode { t.Fatalf("load error = %#v", err) } if test.name == "protected effective path" { sources := loadError.Sources() if len(sources) != 2 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" { t.Fatalf("error source destinations = %#v", sources) } } return } if err != nil { t.Fatalf("ordinary nested version was rejected: %v", err) } result := testLoaded(loaded) if got := result.Config.Collections["entry"]["version"]; got != "v2" { t.Fatalf("nested version = %q", got) } }) } } func TestLayeredDestinationsDoNotChangeRepeatedSourceIdentity(t *testing.T) { t.Parallel() directory := t.TempDir() writeImportFile(t, directory, "shared.yml", "value: shared\n") importerPath := writeImportFile(t, directory, "importer.yml", "configkit: {imports: [{path: shared.yml, destination: [nested]}]}\n") _, err := LoadLayered( Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[importDestinationConfig]{Underlays: []Underlay{{Inputs: []Input{ {Source: File(importerPath), Imports: true, Destination: Keys("imported")}, {Source: File(filepath.Join(directory, "shared.yml")), Destination: Keys("direct")}, }}}}, ) if err == nil { t.Fatal("the same physical source was accepted at two destinations in one group") } loadError, ok := err.(*LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportRepeated { t.Fatalf("load error = %#v", err) } sources := loadError.Sources() if len(sources) != 3 || sources[1].Destination.String() != "/imported/nested" || sources[2].Destination.String() != "/direct" { t.Fatalf("repeated import sources = %#v", sources) } t.Run("placement does not break a physical cycle", func(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: parent.yml, destination: [one]}]}\n") writeImportFile(t, directory, "parent.yml", "configkit: {imports: [{path: main.yml, destination: [two]}]}\n") _, err := LoadLayered(Input{Source: File(mainPath), Imports: true}, testSecretBinding(), LayeredOptions[map[string]any]{}) loadError, ok := err.(*LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportCycle { t.Fatalf("cycle load error = %#v", err) } diagnostic := loadError.Diagnostics()[0] if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" { t.Fatalf("cycle primary location = %#v", diagnostic.Primary) } if sources := loadError.Sources(); len(sources) != 2 || sources[1].Destination.String() != "/one" { t.Fatalf("cycle sources = %#v", sources) } }) }