package configkit_test import ( "errors" "io/fs" "os" "path" "path/filepath" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) func TestFacadeNativeDiscoveryPreservesAliasIdentity(t *testing.T) { root := t.TempDir() for name, contents := range map[string]string{ "data/item.yml": "configkit:\n imports:\n - path: helper.yml\nvalue: item\n", "data/helper.yml": "owner: target\nresource: resource.txt\n", "aliases/helper.yml": "owner: alias\nresource: resource.txt\n", "data/resource.txt": "target resource", "aliases/resource.txt": "alias resource", } { filename := filepath.Join(root, filepath.FromSlash(name)) if err := os.MkdirAll(filepath.Dir(filename), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filename, []byte(contents), 0o600); err != nil { t.Fatal(err) } } for name, target := range map[string]string{"aliases/item.yml": "../data/item.yml", "linked": "data"} { if err := os.Symlink(target, filepath.Join(root, filepath.FromSlash(name))); err != nil { t.Skipf("symlinks unavailable: %v", err) } } filesystem := configkit.NewFilesystem("aliases", os.DirFS(root)) type configuration struct { Value string `yaml:"value"` Owner string `yaml:"owner"` Resource string `yaml:"resource"` } for _, test := range []struct { name, declaration, item, helper, owner, resource string }{ {"file/path", "path: aliases/item.yml", "aliases/item.yml", "aliases/helper.yml", "alias", "alias resource"}, {"file/dir", "dir: aliases\n pattern: item.yml", "aliases/item.yml", "aliases/helper.yml", "alias", "alias resource"}, {"file/glob", "glob: 'aliases/ite*.yml'", "aliases/item.yml", "aliases/helper.yml", "alias", "alias resource"}, {"directory/path", "path: linked/item.yml", "linked/item.yml", "linked/helper.yml", "target", "target resource"}, {"directory/dir", "dir: linked\n pattern: item.yml", "linked/item.yml", "linked/helper.yml", "target", "target resource"}, {"directory/glob", "glob: 'lin*/ite*.yml'", "linked/item.yml", "linked/helper.yml", "target", "target resource"}, } { t.Run(test.name, func(t *testing.T) { result, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+test.declaration+"\n")), configkit.Options[configuration]{ Imports: true, Filesystem: &configkit.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: "."}, }) if err != nil { t.Fatal(err) } if result.Config.Owner != test.owner || result.Config.Value != "item" { t.Fatalf("configuration = %#v", result.Config) } sources := result.Sources() if len(sources) != 3 || sources[1].Path != test.item || sources[2].Path != test.helper { t.Fatalf("source identities = %#v", sources) } for field, source := range map[string]configkit.SourceInfo{"value": sources[1], "resource": sources[2]} { info, ok := result.Metadata().Lookup(configkit.Keys(field)) if !ok || len(info.Origin.Locations) != 1 || info.Origin.Locations[0].SourceID != source.ID { t.Fatalf("%s origin = %#v", field, info) } context, ok := result.SourceContext(source.ID) if !ok || context.BaseDir != path.Dir(source.Path) { t.Fatalf("%s context = %#v", field, context) } contents, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, result.Config.Resource)) if err != nil || string(contents) != test.resource { t.Fatalf("%s resource = %q, %v", field, contents, err) } } }) } // The same native path is still a repeat when separately included. An alias // to a leaf file remains a different native namespace path, as for path imports. for _, selector := range []string{"path: aliases/item.yml", "dir: aliases\n pattern: item.yml", "glob: 'aliases/ite*.yml'"} { _, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n - path: aliases/item.yml\n")), configkit.Options[configuration]{ Imports: true, Filesystem: &configkit.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: "."}, }) var loadError *configkit.LoadError if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeImportRepeated { t.Fatalf("repeated source: %v", err) } _, err = configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n - path: data/item.yml\n")), configkit.Options[configuration]{ Imports: true, Filesystem: &configkit.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: "."}, }) if !errors.As(err, &loadError) || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeComposeConflict { t.Fatalf("distinct native alias paths should reach composition: %v", err) } } }