package configkit_test import ( "embed" "io/fs" "reflect" "strconv" "strings" "testing" "testing/fstest" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) //go:embed testdata/filesystem var filesystemFixtures embed.FS type filesystemFacadeConfig struct { Items map[string]string `yaml:"items"` } func TestFacadeFilesystemSourceImportsOverlayAndReuse(t *testing.T) { fixture, err := fs.Sub(filesystemFixtures, "testdata/filesystem") if err != nil { t.Fatal(err) } namespace := configkit.NewFilesystem("embedded", fixture) copyOfNamespace := namespace source := namespace.File("config/main.yml") for range 2 { result, err := configkit.Load(source, configkit.Options[filesystemFacadeConfig]{ Imports: true, Overlay: "testing", Filesystem: &configkit.FilesystemContext{FS: ©OfNamespace}, }) if err != nil { t.Fatal(err) } if want := (map[string]string{"main": "selected", "shared": "default"}); !reflect.DeepEqual(result.Config.Items, want) { t.Fatalf("items = %#v, want %#v", result.Config.Items, want) } sources := result.Sources() if len(sources) != 3 { t.Fatalf("sources = %#v", sources) } for index, want := range []string{"config/main.yml", "shared.yml", "overlays/testing.yml"} { if sources[index].ID != "source-"+strconv.Itoa(index+1) || sources[index].Path != want || sources[index].Kind != configkit.SourceKindFilesystem { t.Errorf("source %d = %#v", index, sources[index]) } if sources[index].Label != "embedded/"+want { t.Errorf("source label = %q", sources[index].Label) } } if sources[1].ImportedFrom == nil || sources[1].ImportedFrom.SourceID != sources[0].ID || sources[1].ImportedFrom.Line == 0 { t.Fatalf("import location = %#v", sources[1].ImportedFrom) } } } func TestFacadeFilesystemContextsAndIdentity(t *testing.T) { first := configkit.NewFilesystem("same", fstest.MapFS{ "root/config/main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: ../shared.yml}]}\n")}, "root/shared.yml": &fstest.MapFile{Data: []byte("items: {value: first}\n")}, "root/overlays/testing.yml": &fstest.MapFile{Data: []byte("items: {overlay: first}\n")}, }) second := configkit.NewFilesystem("same", fstest.MapFS{ "root/config/main.yml": &fstest.MapFile{Data: []byte("items: {value: second}\n")}, }) context := &configkit.FilesystemContext{Root: "root", FS: &first} result, err := configkit.Load(first.File("root/config/main.yml"), configkit.Options[filesystemFacadeConfig]{Imports: true, Overlay: "testing", Filesystem: context}) if err != nil || result.Config.Items["value"] != "first" || result.Config.Items["overlay"] != "first" { t.Fatalf("contained load = %#v, %v", result, err) } result, err = configkit.Load[filesystemFacadeConfig](second.File("root/config/main.yml")) if err != nil || result.Config.Items["value"] != "second" { t.Fatalf("same-label independent namespace = %#v, %v", result, err) } checkCode(t, second.File("root/config/main.yml"), configkit.Options[filesystemFacadeConfig]{Filesystem: context}, configkit.CodeOptionsInvalid) checkCode(t, first.File("root/config/main.yml"), configkit.Options[filesystemFacadeConfig]{Filesystem: &configkit.FilesystemContext{Root: "root/config", FS: &first}, Imports: true}, configkit.CodeImportOutsideRoot) checkCode(t, configkit.File("testdata/filesystem/config/main.yml"), configkit.Options[filesystemFacadeConfig]{Filesystem: context}, configkit.CodeOptionsInvalid) for _, input := range []struct { name string source configkit.Source kind configkit.SourceKind }{ {"bytes", configkit.Bytes("memory", []byte("configkit: {imports: [{path: shared.yml}]}\n")), configkit.SourceKindBytes}, {"reader", configkit.Reader("stream", strings.NewReader("configkit: {imports: [{path: shared.yml}]}\n")), configkit.SourceKindReader}, } { t.Run(input.name, func(t *testing.T) { result, err := configkit.Load(input.source, configkit.Options[filesystemFacadeConfig]{Imports: true, Filesystem: &configkit.FilesystemContext{Root: "root", BaseDir: "root", FS: &first}}) if err != nil || result.Config.Items["value"] != "first" { t.Fatalf("synthetic import = %#v, %v", result, err) } if got := result.Sources(); len(got) != 2 || got[0].Kind != input.kind || got[0].Path != "" || got[1].Kind != configkit.SourceKindFilesystem { t.Fatalf("sources = %#v", got) } }) } checkCode(t, configkit.Bytes("memory", []byte("items: {}\n")), configkit.Options[filesystemFacadeConfig]{Filesystem: &configkit.FilesystemContext{Root: "root", BaseDir: ".", FS: &first}}, configkit.CodeImportOutsideRoot) result, err = configkit.Load(configkit.Bytes("memory", []byte("items: {value: main}\n")), configkit.Options[filesystemFacadeConfig]{ Overlay: "testing", Filesystem: &configkit.FilesystemContext{Root: "root", BaseDir: "root", FS: &first}, }) if err != nil || result.Config.Items["value"] != "main" || result.Config.Items["overlay"] != "first" { t.Fatalf("synthetic selected overlay = %#v, %v", result, err) } } func TestFacadeSourceKindsIncludeHostAndCLI(t *testing.T) { result, err := configkit.Load(configkit.File("testdata/filesystem/shared.yml"), configkit.Options[filesystemFacadeConfig]{ Overrides: []configkit.Override{{Path: configkit.Keys("items", "shared"), Value: "changed", Flag: "--shared"}}, }) if err != nil { t.Fatal(err) } sources := result.Sources() if len(sources) != 2 || sources[0].Kind != configkit.SourceKindFile || sources[0].Path == "" || sources[1].Kind != configkit.SourceKindCLI || sources[1].Path != "" { t.Fatalf("host and CLI sources = %#v", sources) } } func TestFacadeFilesystemConstructorDiagnostics(t *testing.T) { var nilFilesystem fs.FS var zero configkit.Filesystem for _, source := range []configkit.Source{ zero.File("main.yml"), configkit.NewFilesystem("", fstest.MapFS{}).File("main.yml"), configkit.NewFilesystem("bad\nlabel", fstest.MapFS{}).File("main.yml"), configkit.NewFilesystem("good", nilFilesystem).File("main.yml"), configkit.NewFilesystem("good", fstest.MapFS{}).File("."), configkit.NewFilesystem("good", fstest.MapFS{}).File("../outside.yml"), } { checkCode(t, source, configkit.Options[filesystemFacadeConfig]{}, configkit.CodeSourceInvalid) } } func checkCode(t *testing.T, source configkit.Source, options configkit.Options[filesystemFacadeConfig], want configkit.Code) { t.Helper() _, err := configkit.Load(source, options) loadError, ok := err.(*configkit.LoadError) if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != want { t.Fatalf("error = %#v, want %s", err, want) } }