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 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}]}\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) } }