package engine import ( "errors" "os" "path/filepath" "reflect" "runtime" "testing" ) func TestLoadGlobImportsUseLexicalDeclarationOrderAndNestedDFS(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - glob: fragments/*.yml - path: after.yml items: {root: root} `) aPath := writeImportFile(t, directory, "fragments/a.yml", `configkit: imports: [{path: nested/child.yml}] items: {alpha: alpha} `) childPath := writeImportFile(t, directory, "fragments/nested/child.yml", "items: {child: child}\n") bPath := writeImportFile(t, directory, "fragments/b.yml", "items: {beta: beta}\n") afterPath := writeImportFile(t, directory, "after.yml", "items: {after: after}\n") var previous []SourceInfo for run := 0; run < 2; run++ { result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil { t.Fatal(err) } wantItems := map[string]string{"root": "root", "alpha": "alpha", "child": "child", "beta": "beta", "after": "after"} if !reflect.DeepEqual(result.Config.Items, wantItems) { t.Fatalf("items = %#v, want %#v", result.Config.Items, wantItems) } sources := result.Sources() wantPaths := []string{ canonicalTestPath(t, mainPath), canonicalTestPath(t, aPath), canonicalTestPath(t, childPath), canonicalTestPath(t, bPath), canonicalTestPath(t, afterPath), } gotPaths := make([]string, len(sources)) for index, source := range sources { gotPaths[index] = source.Path } if !reflect.DeepEqual(gotPaths, wantPaths) { t.Fatalf("source paths = %#v, want %#v", gotPaths, wantPaths) } if run != 0 && !reflect.DeepEqual(sources, previous) { t.Fatalf("repeated traversal changed sources: %#v != %#v", sources, previous) } previous = sources } } func TestLoadGlobImportsSupportEscapesClassesAndExplicitConfD(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - glob: 'conf.d/literal\*.yml' - glob: 'conf.d/item-[ab].yml' - glob: 'conf.d/question?.yml' items: {root: root} `) writeImportFile(t, directory, "conf.d/literal*.yml", "items: {literal: literal}\n") writeImportFile(t, directory, "conf.d/item-b.yml", "items: {b: b}\n") writeImportFile(t, directory, "conf.d/item-a.yml", "items: {a: a}\n") writeImportFile(t, directory, "conf.d/question1.yml", "items: {question: question}\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil { t.Fatal(err) } if want := map[string]string{"root": "root", "literal": "literal", "a": "a", "b": "b", "question": "question"}; !reflect.DeepEqual(result.Config.Items, want) { t.Fatalf("items = %#v, want %#v", result.Config.Items, want) } ignoredPath := writeImportFile(t, directory, "conf.d/not-discovered.yml", "unknown: value\n") _ = ignoredPath plainPath := writeImportFile(t, directory, "plain.yml", "items: {root: only}\n") plain, err := testLoad[importTestConfig](File(plainPath), Options[importTestConfig]{Imports: true}) if err != nil || len(plain.Sources()) != 1 || plain.Config.Items["root"] != "only" { t.Fatalf("implicit discovery result = %#v, %v", plain, err) } } func TestLoadGlobImportsRejectInvalidAndUnmatchedPatterns(t *testing.T) { tests := []struct { name string pattern string code Code }{ {name: "bad class", pattern: "empty/[", code: CodeImportInvalid}, {name: "recursive star segment", pattern: "empty/**/value.yml", code: CodeImportInvalid}, {name: "recursive adjacent stars", pattern: "empty/value**.yml", code: CodeImportInvalid}, {name: "trailing separator", pattern: "empty/", code: CodeImportInvalid}, {name: "absolute", pattern: "/tmp/*.yml", code: CodeImportInvalid}, {name: "URL", pattern: "https://example.invalid/*.yml", code: CodeImportInvalid}, {name: "missing parent escape", pattern: "../missing/*.yml", code: CodeImportOutsideRoot}, {name: "unmatched", pattern: "empty/*.yml", code: CodeImportMissing}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() if err := os.Mkdir(filepath.Join(directory, "empty"), 0o755); err != nil { t.Fatal(err) } mainPath := writeGlobRoot(t, directory, test.pattern, test.code == CodeImportMissing) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, test.code) }) } directory := t.TempDir() if err := os.Mkdir(filepath.Join(directory, "empty"), 0o755); err != nil { t.Fatal(err) } mainPath := writeGlobRoot(t, directory, "empty/*.yml", false) result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil || len(result.Sources()) != 1 { t.Fatalf("optional unmatched glob = %#v, %v", result, err) } } func TestLoadGlobImportsPreserveIdentityAndFileRules(t *testing.T) { t.Run("overlapping declarations", func(t *testing.T) { directory := t.TempDir() writeImportFile(t, directory, "conf/value.yml", "items: {value: value}\n") mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - glob: conf/*.yml - glob: conf/value.* `) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportRepeated) }) t.Run("matched directory", func(t *testing.T) { directory := t.TempDir() if err := os.MkdirAll(filepath.Join(directory, "conf/matched.yml"), 0o755); err != nil { t.Fatal(err) } mainPath := writeGlobRoot(t, directory, "conf/*.yml", false) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeSourceNotRegular) }) t.Run("hard links stay distinct", func(t *testing.T) { directory := t.TempDir() one := writeImportFile(t, directory, "conf/one.yml", "items: {same: value}\n") if err := os.Link(one, filepath.Join(directory, "conf/two.yml")); err != nil { t.Skipf("hard links unavailable: %v", err) } mainPath := writeGlobRoot(t, directory, "conf/*.yml", false) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeComposeConflict) }) } func TestLoadGlobImportsConfineSymlinkDirectories(t *testing.T) { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { t.Skip("initial filesystem contract covers Linux and macOS") } t.Run("contained absolute target", func(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target") writeImportFile(t, target, "value.yml", "items: {linked: linked}\n") if err := os.Symlink(target, filepath.Join(root, "linked")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeGlobRoot(t, root, "linked/*.yml", false) result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) if err != nil || result.Config.Items["linked"] != "linked" { var loadError *LoadError if errors.As(err, &loadError) { t.Fatalf("contained directory symlink = %#v, diagnostics %#v", result, loadError.Diagnostics()) } t.Fatalf("contained directory symlink = %#v, %v", result, err) } }) t.Run("escaping target", func(t *testing.T) { root := t.TempDir() outside := t.TempDir() writeImportFile(t, outside, "value.yml", "items: {escaped: escaped}\n") if err := os.Symlink(outside, filepath.Join(root, "linked")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeGlobRoot(t, root, "linked/*.yml", false) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportOutsideRoot) }) } func TestOptionalGlobDoesNotSuppressDirectoryPermissionFailure(t *testing.T) { directory := t.TempDir() locked := filepath.Join(directory, "locked") if err := os.Mkdir(locked, 0o700); err != nil { t.Fatal(err) } if err := os.Chmod(locked, 0); err != nil { t.Skipf("cannot create unreadable directory fixture: %v", err) } defer func() { _ = os.Chmod(locked, 0o700) }() if opened, err := os.Open(locked); err == nil { _ = opened.Close() t.Skip("filesystem does not enforce the unreadable-directory fixture") } mainPath := writeGlobRoot(t, directory, "locked/*.yml", false) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Imports: true}, CodeImportPermission) } func TestGlobTraversalBoundsNonmatchesAndAcceptedCandidates(t *testing.T) { directory := t.TempDir() for _, name := range []string{"one.txt", "two.txt", "three.txt"} { writeImportFile(t, directory, "many/"+name, "ignored\n") } mainPath := writeGlobRoot(t, directory, "many/*.yml", false) main := parseImportMain(t, mainPath) limits := defaultImportLimits() limits.entries = 2 request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil) _, _, diagnostics := testAcquireImportWithLimits(request, limits) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("entry-budget diagnostics = %#v", diagnostics) } for _, name := range []string{"a.yml", "b.yml"} { writeImportFile(t, directory, "matches/"+name, "items: {"+name[:1]+": value}\n") } mainPath = writeGlobRoot(t, directory, "matches/*.yml", false) main = parseImportMain(t, mainPath) limits = defaultImportLimits() limits.sources = 2 request = testImportRequest(File(mainPath), nil, main, Path{}, nil, nil) _, _, diagnostics = testAcquireImportWithLimits(request, limits) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("candidate-budget diagnostics = %#v", diagnostics) } } func TestFilesystemContextIsValidatedWithoutImportsOrActiveOverlay(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "items: {root: root}\n") result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{ Overlay: "default", Filesystem: &FilesystemContext{Root: directory}, }) if err != nil || result.Config.Items["root"] != "root" { t.Fatalf("file filesystem context = %#v, %v", result, err) } requireLoadCode(t, File(mainPath), Options[importTestConfig]{Filesystem: &FilesystemContext{BaseDir: directory, Root: directory}}, CodeOptionsInvalid) requireLoadCode(t, File(mainPath), Options[importTestConfig]{Filesystem: &FilesystemContext{Root: filepath.Join(directory, "missing")}}, CodeImportInvalid) input := Bytes("memory.yml", []byte("items: {root: memory}\n")) result, err = testLoad[importTestConfig](input, Options[importTestConfig]{Filesystem: &FilesystemContext{BaseDir: directory, Root: directory}}) if err != nil || result.Config.Items["root"] != "memory" { t.Fatalf("byte filesystem context = %#v, %v", result, err) } requireLoadCode(t, Bytes("memory.yml", []byte("{}\n")), Options[importTestConfig]{Filesystem: &FilesystemContext{BaseDir: directory}}, CodeOptionsInvalid) defaultsOnly, err := testLoad[importTestConfig](NoSource(), Options[importTestConfig]{Overlay: "default"}) if err != nil || defaultsOnly == nil { t.Fatalf("default overlay with NoSource = %#v, %v", defaultsOnly, err) } if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { selectedLink := filepath.Join(directory, "selected-link.yml") if err := os.Symlink(mainPath, selectedLink); err == nil { linked, err := testLoad[importTestConfig](File(selectedLink), Options[importTestConfig]{Filesystem: &FilesystemContext{Root: directory}}) if err != nil || linked.Sources()[0].Path != canonicalTestPath(t, mainPath) { t.Fatalf("selected symlink identity = %#v, %v", linked, err) } } } } func TestNoSourceImportsAndFilesystemContext(t *testing.T) { t.Parallel() directory := t.TempDir() for _, imports := range []bool{false, true} { name := "imports disabled" if imports { name = "imports enabled" } t.Run(name, func(t *testing.T) { result, err := testLoad[importTestConfig](NoSource(), Options[importTestConfig]{ Imports: imports, Filesystem: &FilesystemContext{Root: directory}, }) if err != nil { t.Fatal(err) } if sources := result.Sources(); len(sources) != 0 { t.Fatalf("NoSource sources = %#v", sources) } if _, ok := result.Metadata().Lookup(Keys("items", "root")); ok { t.Fatal("NoSource contributed explicit input") } }) } outside := t.TempDir() for _, test := range []struct { name string context FilesystemContext code Code }{ {name: "missing root", context: FilesystemContext{BaseDir: directory}, code: CodeOptionsInvalid}, {name: "relative root", context: FilesystemContext{Root: "."}, code: CodeOptionsInvalid}, {name: "missing root directory", context: FilesystemContext{Root: filepath.Join(directory, "missing")}, code: CodeImportInvalid}, {name: "base outside root", context: FilesystemContext{BaseDir: outside, Root: directory}, code: CodeImportOutsideRoot}, } { t.Run(test.name, func(t *testing.T) { requireLoadCode(t, NoSource(), Options[importTestConfig]{Filesystem: &test.context}, test.code) }) } } func writeGlobRoot(t *testing.T, directory, pattern string, required bool) string { t.Helper() requiredLine := "" if required { requiredLine = "\n required: true" } return writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - glob: '"+pattern+"'"+requiredLine+"\n") }