221 lines
10 KiB
Go
221 lines
10 KiB
Go
package configkit_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
configkit "go.maximumdirect.net/configkit"
|
|
)
|
|
|
|
func TestMainRelativeCompanion(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "main.yml"), []byte("count: 3\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "extra.yml"), []byte("settings: {companion: host}\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
binding := configkit.MainRelative()
|
|
companion := configkit.Input{Source: configkit.Bytes("companions", []byte("config: {imports: [{path: extra.yml}]}\n")), Imports: true, MainRelative: binding}
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(filepath.Join(dir, "main.yml"))}, configkit.LayeredOptions[layeredFacadeConfig]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{companion}}}})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 3 || result.Config.Settings["companion"] != "host" {
|
|
t.Fatalf("configuration = %#v", result.Config)
|
|
}
|
|
native := configkit.NewFilesystem("bundle", fstest.MapFS{"dir/main.yml": &fstest.MapFile{Data: []byte("count: 4\n")}, "dir/extra.yml": &fstest.MapFile{Data: []byte("settings: {companion: native}\n")}})
|
|
result, err = configkit.LoadLayered(configkit.Input{Source: native.File("dir/main.yml")}, configkit.LayeredOptions[layeredFacadeConfig]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{companion}}}})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 4 || result.Config.Settings["companion"] != "native" {
|
|
t.Fatalf("configuration = %#v", result.Config)
|
|
}
|
|
}
|
|
|
|
func TestMainRelativeContextSelection(t *testing.T) {
|
|
root := t.TempDir()
|
|
base := filepath.Join(root, "main")
|
|
if err := os.Mkdir(base, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for path, value := range map[string]string{"main/main.yml": "count: 1\n", "main/extra.yml": "settings: {value: derived}\n", "extra.yml": "settings: {value: fallback}\n"} {
|
|
if err := os.WriteFile(filepath.Join(root, path), []byte(value), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
link := filepath.Join(root, "link.yml")
|
|
if err := os.Symlink(filepath.Join(base, "main.yml"), link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
companion := configkit.Input{Source: configkit.Bytes("companion", []byte("config: {imports: [{path: extra.yml}]}\n")), Imports: true, MainRelative: &configkit.MainRelativeContext{Fallback: &configkit.FilesystemContext{Root: root, BaseDir: root}}}
|
|
opts := configkit.LayeredOptions[layeredFacadeConfig]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{companion}}}}
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(link), Filesystem: &configkit.FilesystemContext{Root: root}}, opts)
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if got := result.Config.Settings["value"]; got != "derived" {
|
|
t.Fatalf("derived value = %q", got)
|
|
}
|
|
result, err = configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, opts)
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if got := result.Config.Settings["value"]; got != "fallback" {
|
|
t.Fatalf("fallback value = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestMainRelativeWithoutBase(t *testing.T) {
|
|
tests := []struct {
|
|
name, source string
|
|
requireBase bool
|
|
wantCode configkit.Code
|
|
}{
|
|
{"optional", "config: {imports: [{path: missing.yml}]}\ncount: 7\n", false, ""},
|
|
{"required", "config: {imports: [{path: missing.yml, required: true}]}\n", false, configkit.CodeImportMissing},
|
|
{"required directory", "config: {imports: [{dir: missing, required: true}]}\n", false, configkit.CodeImportMissing},
|
|
{"required glob", "config: {imports: [{glob: '*.yml', required: true}]}\n", false, configkit.CodeImportMissing},
|
|
{"empty required", "config: {imports: [{path: [], required: true}]}\ncount: 7\n", false, ""},
|
|
{"base required", "count: 7\n", true, configkit.CodeOptionsInvalid},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
input := configkit.Input{Source: configkit.Bytes("companion", []byte(test.source)), Imports: true, MainRelative: &configkit.MainRelativeContext{RequireBase: test.requireBase}}
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[layeredFacadeConfig]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}}})
|
|
if test.wantCode != "" {
|
|
if err == nil {
|
|
t.Fatal("expected failure")
|
|
}
|
|
for _, d := range err.(*configkit.LoadError).Diagnostics() {
|
|
if d.Code == test.wantCode {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("diagnostics: %#v", err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 7 {
|
|
t.Fatalf("config = %#v", result.Config)
|
|
}
|
|
if sources := result.Sources(); len(sources) != 1 || len(sources[0].EmptyImportSelections) != 0 {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMainRelativeWithoutBaseSequenceItem(t *testing.T) {
|
|
type item struct {
|
|
Name string `yaml:"name"`
|
|
}
|
|
type config struct {
|
|
Items []item `yaml:"items"`
|
|
}
|
|
input := configkit.Input{Source: configkit.Bytes("item", []byte("config: {imports: [{path: missing.yml}]}\nname: example\n")), Imports: true, MainRelative: configkit.MainRelative(), Destination: configkit.Keys("items"), PlacementMode: configkit.PlacementModeSequenceItem}
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[config]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}}})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if len(result.Config.Items) != 1 || result.Config.Items[0].Name != "example" {
|
|
t.Fatalf("config = %#v", result.Config)
|
|
}
|
|
}
|
|
|
|
func TestMainRelativeWithoutBaseImportDefaults(t *testing.T) {
|
|
required := true
|
|
tests := []struct {
|
|
name, source string
|
|
imports bool
|
|
requireBase bool
|
|
code configkit.Code
|
|
}{
|
|
{"default required", "config: {imports: [{path: missing.yml}]}\n", true, false, configkit.CodeImportMissing},
|
|
{"malformed", "config: {imports: [{path: 42}]}\n", true, false, configkit.CodeImportInvalid},
|
|
{"imports disabled", "config: {imports: [{path: missing.yml}]}\n", false, false, configkit.CodeImportDisabled},
|
|
{"base required without imports", "count: 1\n", false, true, configkit.CodeOptionsInvalid},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
input := configkit.Input{Source: configkit.Bytes("companion", []byte(test.source)), Imports: test.imports, MainRelative: &configkit.MainRelativeContext{RequireBase: test.requireBase}}
|
|
_, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[layeredFacadeConfig]{ImportDefaults: configkit.ImportDefaults{Required: &required}, Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}}})
|
|
if err == nil {
|
|
t.Fatal("expected failure")
|
|
}
|
|
for _, diagnostic := range err.(*configkit.LoadError).Diagnostics() {
|
|
if diagnostic.Code == test.code {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("diagnostics = %#v", err.(*configkit.LoadError).Diagnostics())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLayeredOverlayContextSelection(t *testing.T) {
|
|
root := t.TempDir()
|
|
base := filepath.Join(root, "base")
|
|
for _, dir := range []string{filepath.Join(root, "overlays"), filepath.Join(base, "overlays")} {
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for path, content := range map[string]string{"base/main.yml": "count: 1\n", "base/overlays/test.yml": "count: 2\n", "overlays/test.yml": "count: 3\n"} {
|
|
if err := os.WriteFile(filepath.Join(root, path), []byte(content), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
main := configkit.Input{Source: configkit.File(filepath.Join(base, "main.yml")), Filesystem: &configkit.FilesystemContext{Root: root}}
|
|
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[layeredFacadeConfig]{Overlay: "test"})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 2 {
|
|
t.Fatalf("derived overlay = %d", result.Config.Count)
|
|
}
|
|
result, err = configkit.LoadLayered(main, configkit.LayeredOptions[layeredFacadeConfig]{Overlay: "test", OverlayFilesystem: &configkit.FilesystemContext{Root: root, BaseDir: base}})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 3 {
|
|
t.Fatalf("explicit overlay = %d", result.Config.Count)
|
|
}
|
|
result, err = configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[layeredFacadeConfig]{Overlay: "test", OverlayFallback: &configkit.FilesystemContext{Root: root, BaseDir: base}})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 2 {
|
|
t.Fatalf("fallback overlay = %d", result.Config.Count)
|
|
}
|
|
}
|
|
|
|
func TestLayeredOverlayNativeContext(t *testing.T) {
|
|
filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"dir/main.yml": &fstest.MapFile{Data: []byte("count: 1\n")},
|
|
"dir/overlays/test.yml": &fstest.MapFile{Data: []byte("count: 9\n")},
|
|
})
|
|
main := configkit.Input{Source: filesystem.File("dir/main.yml"), Filesystem: &configkit.FilesystemContext{Root: ".", FS: &filesystem}}
|
|
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[layeredFacadeConfig]{Overlay: "test"})
|
|
if err != nil {
|
|
t.Fatalf("%v: %#v", err, err.(*configkit.LoadError).Diagnostics())
|
|
}
|
|
if result.Config.Count != 9 {
|
|
t.Fatalf("config = %#v", result.Config)
|
|
}
|
|
_, err = configkit.LoadLayered(main, configkit.LayeredOptions[layeredFacadeConfig]{Overlay: "missing", OverlayFallback: &configkit.FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem}})
|
|
if err == nil {
|
|
t.Fatal("missing selected overlay succeeded")
|
|
}
|
|
for _, diagnostic := range err.(*configkit.LoadError).Diagnostics() {
|
|
if diagnostic.Code == configkit.CodeOverlayMissing {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("diagnostics = %#v", err.(*configkit.LoadError).Diagnostics())
|
|
}
|