package engine import ( "errors" "os" "path/filepath" "reflect" "runtime" "strconv" "strings" "testing" ) type importTestConfig struct { Settings struct { Root string `yaml:"root"` First string `yaml:"first"` Nested string `yaml:"nested"` Second string `yaml:"second"` } `yaml:"settings"` Version string `yaml:"version"` Items map[string]string `yaml:"items"` } func TestLoadFormerDirectiveKeyIsApplicationData(t *testing.T) { type config struct { Legacy struct { Imports []struct { Path string `yaml:"path"` } `yaml:"imports"` } `yaml:"_configkit"` } for _, imports := range []bool{false, true} { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "_configkit:\n imports:\n - path: nonexistent.yml\n") result, err := testLoad[config](File(mainPath), Options[config]{Imports: imports}) if err != nil { t.Fatalf("Imports=%v: %v", imports, err) } if len(result.Config.Legacy.Imports) != 1 || result.Config.Legacy.Imports[0].Path != "nonexistent.yml" || len(result.Sources()) != 1 { t.Fatalf("Imports=%v: former directive was not retained as application data: %#v", imports, result) } } } func TestLoadTraversesPathImportsDepthFirstAndComposesOrigins(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: first.yml - path: second.yml settings: root: root `) firstPath := writeImportFile(t, directory, "first.yml", `configkit: imports: - path: nested/third.yml settings: first: first `) thirdPath := writeImportFile(t, directory, "nested/third.yml", "settings:\n nested: nested\n") secondPath := writeImportFile(t, directory, "second.yml", "settings:\n second: second\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil { t.Fatal(err) } if got := result.Config.Settings; got.Root != "root" || got.First != "first" || got.Nested != "nested" || got.Second != "second" { t.Fatalf("composed settings = %#v", got) } sources := result.Sources() wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, firstPath), canonicalTestPath(t, thirdPath), canonicalTestPath(t, secondPath)} gotPaths := make([]string, len(sources)) for index, source := range sources { gotPaths[index] = source.Path if source.ID != "source-"+strconv.Itoa(index+1) { t.Errorf("source %d ID = %q", index, source.ID) } if index > 0 && source.ImportedFrom == nil { t.Errorf("source %d has no import location", index) } } if !reflect.DeepEqual(gotPaths, wantPaths) { t.Fatalf("source paths = %#v, want depth-first %#v", gotPaths, wantPaths) } info, ok := result.Metadata().Lookup(Keys("settings")) if !ok || !info.Explicit || len(info.Origin.Locations) != 4 { t.Fatalf("settings metadata = %#v, %v", info, ok) } } func TestLoadImportsFromProfilesDirectoryAsBaseInput(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: profiles/shared.yml}]}\n") writeImportFile(t, directory, "profiles/shared.yml", "settings: {root: imported}\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil || result.Config.Settings.Root != "imported" || len(result.Sources()) != 2 || result.Sources()[1].Layer != LayerBase { t.Fatalf("profiles import = %#v, %v", result, err) } } func TestLoadRejectsImportConflictsAndFragmentVersions(t *testing.T) { t.Run("all duplicate claimants", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: one.yml - path: two.yml - path: three.yml `) for index, name := range []string{"one.yml", "two.yml", "three.yml"} { writeImportFile(t, directory, name, "settings:\n root: "+string(rune('a'+index))+"\n") } loadError := requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeComposeConflict) diagnostic := loadError.Diagnostics()[0] if diagnostic.Path.String() != "/settings/root" || diagnostic.Primary == nil || len(diagnostic.Related) != 2 { t.Fatalf("conflict diagnostic = %#v", diagnostic) } }) versionRule := &VersionRule{Path: Keys("settings", "version"), Check: func(Version, *Validation) error { return nil }} for _, test := range []struct { name string fragment string }{ {name: "exact integer", fragment: "settings:\n version: 4\n"}, {name: "ancestor scalar", fragment: "settings: blocked\n"}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: fragment.yml\n") writeImportFile(t, directory, "fragment.yml", test.fragment) loadError := requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true, Definition: Definition[importTestConfig]{Version: versionRule}}, CodeImportVersion) diagnostic := loadError.Diagnostics()[0] if diagnostic.Path.String() != "/settings/version" || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" { t.Fatalf("version placement diagnostic = %#v", diagnostic) } }) } } func TestLoadImportDirectiveValidationAndTemporaryGlobGate(t *testing.T) { tests := []struct { name string body string code Code }{ {name: "directive kind", body: "configkit: []\n", code: CodeImportInvalid}, {name: "unknown directive field", body: "configkit: {unknown: true}\n", code: CodeImportInvalid}, {name: "imports kind", body: "configkit: {imports: {}}\n", code: CodeImportInvalid}, {name: "declaration kind", body: "configkit: {imports: [file.yml]}\n", code: CodeImportInvalid}, {name: "missing target", body: "configkit: {imports: [{optional: true}]}\n", code: CodeImportInvalid}, {name: "two targets", body: "configkit: {imports: [{path: a.yml, glob: '*.yml'}]}\n", code: CodeImportInvalid}, {name: "unknown declaration field", body: "configkit: {imports: [{path: a.yml, extra: true}]}\n", code: CodeImportInvalid}, {name: "optional kind", body: "configkit: {imports: [{path: a.yml, optional: 'yes'}]}\n", code: CodeImportInvalid}, {name: "nested directive", body: "nested: {configkit: {}}\n", code: CodeImportInvalid}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", test.body) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, test.code) }) } } func TestLoadImportMissingContainmentAndRegularFileRules(t *testing.T) { t.Run("optional missing", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: missing.yml optional: true settings: {root: retained} `) result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil || result.Config.Settings.Root != "retained" || len(result.Sources()) != 1 { t.Fatalf("optional result = %#v, %v", result, err) } }) tests := []struct { name string path string setup func(*testing.T, string) code Code }{ {name: "required missing", path: "missing.yml", code: CodeImportMissing}, {name: "parent traversal", path: "../outside.yml", setup: func(t *testing.T, directory string) { writeImportFile(t, filepath.Dir(directory), "outside.yml", "settings: {root: outside}\n") }, code: CodeImportOutsideRoot}, {name: "directory", path: "directory", setup: func(t *testing.T, directory string) { if err := os.Mkdir(filepath.Join(directory, "directory"), 0o755); err != nil { t.Fatal(err) } }, code: CodeSourceNotRegular}, {name: "absolute", path: "/tmp/configkit.yml", code: CodeImportInvalid}, {name: "network URL", path: "https://example.invalid/config.yml", code: CodeImportInvalid}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() if test.setup != nil { test.setup(t, directory) } mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: \""+test.path+"\"\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, test.code) }) } } func TestLoadImportIdentityReportsCyclesAndRepeatedRoutes(t *testing.T) { t.Run("cycle", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports: [{path: a.yml}]\n") writeImportFile(t, directory, "a.yml", "configkit:\n imports: [{path: b.yml}]\n") writeImportFile(t, directory, "b.yml", "configkit:\n imports: [{path: main.yml}]\n") loadError := requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportCycle) diagnostic := loadError.Diagnostics()[0] if diagnostic.Primary == nil || len(diagnostic.Related) != 2 || !strings.Contains(diagnostic.Message, "main.yml") || !strings.Contains(diagnostic.Message, "a.yml") || !strings.Contains(diagnostic.Message, "b.yml") { t.Fatalf("cycle diagnostic = %#v", diagnostic) } }) t.Run("diamond", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports: [{path: left.yml}, {path: right.yml}]\n") writeImportFile(t, directory, "left.yml", "configkit:\n imports: [{path: shared.yml}]\n") writeImportFile(t, directory, "right.yml", "configkit:\n imports: [{path: shared.yml}]\n") writeImportFile(t, directory, "shared.yml", "settings: {root: shared}\n") loadError := requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportRepeated) diagnostic := loadError.Diagnostics()[0] if diagnostic.Primary == nil || len(diagnostic.Related) < 3 { t.Fatalf("repeated-route diagnostic = %#v", diagnostic) } }) t.Run("symlink aliases", func(t *testing.T) { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { t.Skip("initial filesystem contract covers Linux and macOS") } directory := t.TempDir() writeImportFile(t, directory, "target.yml", "settings: {root: target}\n") if err := os.Symlink("target.yml", filepath.Join(directory, "first-link.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } if err := os.Symlink("target.yml", filepath.Join(directory, "second-link.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: first-link.yml}, {path: second-link.yml}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportRepeated) }) } func TestLoadImportsCanonicalSymlinksAndKeepsHardLinksDistinct(t *testing.T) { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { t.Skip("initial filesystem contract covers Linux and macOS") } t.Run("contained absolute symlink", func(t *testing.T) { directory := t.TempDir() target := writeImportFile(t, directory, "target.yml", "settings: {first: linked}\n") if err := os.Symlink(target, filepath.Join(directory, "link.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: link.yml}]}\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil || result.Config.Settings.First != "linked" || result.Sources()[1].Path != canonicalTestPath(t, target) { t.Fatalf("symlink result = %#v, %v", result, err) } }) t.Run("symlink escape", func(t *testing.T) { root := t.TempDir() outside := t.TempDir() target := writeImportFile(t, outside, "outside.yml", "settings: {root: outside}\n") if err := os.Symlink(target, filepath.Join(root, "escape.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeImportFile(t, root, "main.yml", "configkit: {imports: [{path: escape.yml}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportOutsideRoot) }) t.Run("hard links are path identities", func(t *testing.T) { directory := t.TempDir() one := writeImportFile(t, directory, "one.yml", "settings: {first: one}\n") if err := os.Link(one, filepath.Join(directory, "two.yml")); err != nil { t.Skipf("hard links unavailable: %v", err) } mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: one.yml}, {path: two.yml}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeComposeConflict) }) t.Run("canonicalization failure", func(t *testing.T) { directory := t.TempDir() if err := os.Symlink("loop.yml", filepath.Join(directory, "loop.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: loop.yml}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportInvalid) }) } func TestLoadByteAndReaderImportsRequireExplicitFilesystemContext(t *testing.T) { directory := t.TempDir() writeImportFile(t, directory, "fragment.yml", "settings: {first: imported}\n") input := []byte("configkit: {imports: [{path: fragment.yml}]}\n") context := &FilesystemContext{BaseDir: directory, Root: directory} for _, test := range []struct { name string source func() Source }{ {name: "bytes URI label", source: func() Source { return Bytes("s3://bucket/main.yml", input) }}, {name: "reader URI label", source: func() Source { return Reader("https://example.invalid/main.yml", strings.NewReader(string(input))) }}, } { t.Run(test.name, func(t *testing.T) { result, err := testLoad[importTestConfig](test.source(), Options[importTestConfig]{Imports: true, Filesystem: context}) if err != nil || result.Config.Settings.First != "imported" { t.Fatalf("explicit filesystem result = %#v, %v", result, err) } }) } requireLoadCode(t, Bytes("memory.yml", input), Options[importTestConfig]{Imports: true}, CodeOptionsInvalid) requireLoadCode(t, Bytes("memory.yml", input), Options[importTestConfig]{Imports: true, Filesystem: &FilesystemContext{BaseDir: "relative", Root: directory}}, CodeOptionsInvalid) requireLoadCode(t, Bytes("memory.yml", input), Options[importTestConfig]{Imports: true, Filesystem: &FilesystemContext{BaseDir: directory, Root: t.TempDir()}}, CodeImportOutsideRoot) } func TestFileImportRootCanContainTheSelectedFileDirectory(t *testing.T) { root := t.TempDir() mainPath := writeImportFile(t, root, "configs/main.yml", "configkit: {imports: [{path: ../shared.yml}]}\n") writeImportFile(t, root, "shared.yml", "settings: {root: shared}\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{ Imports: true, Filesystem: &FilesystemContext{Root: root}, }) if err != nil || result.Config.Settings.Root != "shared" { t.Fatalf("explicit-root result = %#v, %v", result, err) } requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true, Filesystem: &FilesystemContext{BaseDir: filepath.Dir(mainPath), Root: root}}, CodeOptionsInvalid) } func TestImportedParseAndDecodeDiagnosticsKeepTheirSource(t *testing.T) { t.Run("parse", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: broken.yml}]}\n") writeImportFile(t, directory, "broken.yml", "settings: [\n") loadError := requireAnyLoadError(t, File(mainPath), Options[importTestConfig]{Imports: true}) diagnostics := loadError.Diagnostics() if diagnostics[0].Code != CodeYAMLInvalid || diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" || len(loadError.Sources()) != 2 { t.Fatalf("parse failure = diagnostics %#v, sources %#v", diagnostics, loadError.Sources()) } }) t.Run("decode", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: unknown.yml}]}\n") writeImportFile(t, directory, "unknown.yml", "unknown: value\n") loadError := requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeDecodeUnknownField) diagnostic := loadError.Diagnostics()[0] if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" { t.Fatalf("decode diagnostic = %#v", diagnostic) } }) } func TestOptionalImportDoesNotSuppressUnsafeOrUnreadableTargets(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "outside.yml", "configkit: {imports: [{path: ../missing.yml, optional: true}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportOutsideRoot) locked := writeImportFile(t, directory, "locked.yml", "settings: {root: locked}\n") if err := os.Chmod(locked, 0); err != nil { t.Skipf("cannot create unreadable fixture: %v", err) } defer func() { _ = os.Chmod(locked, 0o600) }() if file, err := os.Open(locked); err == nil { _ = file.Close() t.Skip("filesystem does not enforce the unreadable-file fixture") } mainPath = writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: locked.yml, optional: true}]}\n") requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeSourcePermission) } func TestImportTraversalEnforcesPrivateBudgets(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: one.yml}, {path: two.yml}]}\n") writeImportFile(t, directory, "one.yml", "settings: {first: one}\n") writeImportFile(t, directory, "two.yml", "settings: {second: two}\n") main := parseImportMain(t, mainPath) for _, test := range []struct { name string limits importLimits }{ {name: "source count", limits: importLimits{depth: 8, sources: 2, bytes: maxTotalBytes, nodes: maxDocumentNodes}}, {name: "aggregate bytes", limits: importLimits{depth: 8, sources: 8, bytes: main.bytes + 1, nodes: maxDocumentNodes}}, {name: "aggregate nodes", limits: importLimits{depth: 8, sources: 8, bytes: maxTotalBytes, nodes: countInputNodes(main.document) + 1}}, } { t.Run(test.name, func(t *testing.T) { _, _, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, test.limits) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("budget diagnostics = %#v", diagnostics) } }) } writeImportFile(t, directory, "one.yml", "configkit: {imports: [{path: nested.yml}]}\n") writeImportFile(t, directory, "nested.yml", "settings: {nested: nested}\n") main = parseImportMain(t, mainPath) _, _, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, importLimits{depth: 1, sources: 8, bytes: maxTotalBytes, nodes: maxDocumentNodes}) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("depth diagnostics = %#v", diagnostics) } } func TestImportTraversalAcceptsExactPrivateBudgets(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: imported.yml}]}\n") importedPath := writeImportFile(t, directory, "imported.yml", "settings: {first: imported}\n") main := parseImportMain(t, mainPath) imported := parseImportMain(t, importedPath) data, err := os.ReadFile(importedPath) if err != nil { t.Fatal(err) } limits := importLimits{ depth: 1, sources: 2, bytes: main.bytes + int64(len(data)), nodes: countInputNodes(main.document) + countInputNodes(imported.document), } composed, sources, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, limits) if len(diagnostics) != 0 || composed == nil || len(sources) != 2 { t.Fatalf("exact-limit composition = (%#v, %#v, %#v)", composed, sources, diagnostics) } } func writeImportFile(t *testing.T, root, relative, contents string) string { t.Helper() filename := filepath.Join(root, filepath.FromSlash(relative)) if err := os.MkdirAll(filepath.Dir(filename), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filename, []byte(contents), 0o600); err != nil { t.Fatal(err) } return filename } func canonicalTestPath(t *testing.T, filename string) string { t.Helper() canonical, err := canonicalExistingPath(filename) if err != nil { t.Fatal(err) } return canonical } func requireLoadCode(t *testing.T, source Source, options Options[importTestConfig], code Code) *LoadError { t.Helper() loadError := requireAnyLoadError(t, source, options) diagnostics := loadError.Diagnostics() if len(diagnostics) == 0 || diagnostics[0].Code != code { t.Fatalf("diagnostics = %#v, want %s", diagnostics, code) } return loadError } func requireAnyLoadError(t *testing.T, source Source, options Options[importTestConfig]) *LoadError { t.Helper() result, err := testLoad[importTestConfig](source, options) if result != nil || err == nil { t.Fatalf("result, error = %#v, %v", result, err) } var loadError *LoadError if !errors.As(err, &loadError) { t.Fatalf("error type = %T", err) } return loadError } func parseImportMain(t *testing.T, filename string) parsedSource { t.Helper() parsed, diagnostics := parseSource(File(filename), "source-1", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatalf("parse diagnostics = %#v", diagnostics) } return parsed }