134 lines
5.8 KiB
Go
134 lines
5.8 KiB
Go
package configkit_test
|
|
|
|
import (
|
|
"math"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
type mappingFacadeConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Count uint64 `yaml:"count"`
|
|
Optional *string `yaml:"optional"`
|
|
Items []string `yaml:"items"`
|
|
Name string `yaml:"name"`
|
|
}
|
|
|
|
func TestFacadeMappingSnapshotsAndMatchesYAML(t *testing.T) {
|
|
items := []any{"one", "two"}
|
|
values := map[string]any{
|
|
"name": "original", "enabled": false, "count": ^uint64(0),
|
|
"optional": nil, "items": items,
|
|
}
|
|
source := configkit.Mapping("programmatic", values)
|
|
values["name"] = "mutated"
|
|
items[0] = "mutated"
|
|
for range 2 {
|
|
result, err := configkit.Load[mappingFacadeConfig](source)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := mappingFacadeConfig{Count: ^uint64(0), Items: []string{"one", "two"}, Name: "original"}
|
|
if !reflect.DeepEqual(result.Config, want) {
|
|
t.Fatalf("mapping config = %#v, want %#v", result.Config, want)
|
|
}
|
|
if sources := result.Sources(); len(sources) != 1 || sources[0].ID != "source-1" || sources[0].Kind != configkit.SourceKindMapping || sources[0].Layer != configkit.LayerBase || sources[0].Path != "" {
|
|
t.Fatalf("mapping sources = %#v", sources)
|
|
}
|
|
info, ok := result.Metadata().Lookup(configkit.Keys("name"))
|
|
if !ok || len(info.Origin.Locations) != 1 || info.Origin.Locations[0].SourceID != "source-1" || info.Origin.Locations[0].Line != 0 {
|
|
t.Fatalf("mapping name origin = %#v, %v", info, ok)
|
|
}
|
|
fromYAML, err := configkit.Load[mappingFacadeConfig](configkit.Bytes("equivalent", []byte("enabled: false\ncount: 18446744073709551615\noptional: null\nitems: [one, two]\nname: original\n")))
|
|
if err != nil || !reflect.DeepEqual(result.Config, fromYAML.Config) {
|
|
t.Fatalf("equivalent YAML = %#v, %v", fromYAML, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFacadeMappingNullEmptyAndVersion(t *testing.T) {
|
|
result, err := configkit.Load[mappingFacadeConfig](configkit.Mapping("empty", map[string]any{}))
|
|
if err != nil || len(result.Sources()) != 1 {
|
|
t.Fatalf("empty mapping = %#v, %v", result, err)
|
|
}
|
|
var nilSequence []any
|
|
var nilMap map[string]any
|
|
var checked string
|
|
result, err = configkit.Load(configkit.Mapping("nullable", map[string]any{
|
|
"items": nilSequence, "optional": nilMap, "enabled": false, "count": uint64(0),
|
|
}), configkit.Options[mappingFacadeConfig]{Definition: configkit.Definition[mappingFacadeConfig]{
|
|
Version: &configkit.VersionRule{Path: configkit.Keys("count"), Check: func(version configkit.Version, validation *configkit.Validation) error {
|
|
checked = version.Text
|
|
return nil
|
|
}},
|
|
}})
|
|
if err != nil || checked != "0" || result.Config.Items != nil || result.Config.Optional != nil {
|
|
t.Fatalf("null and zero mapping = %#v, checked %q, %v", result, checked, err)
|
|
}
|
|
info, ok := result.Metadata().Lookup(configkit.Keys("items"))
|
|
if !ok || !info.Null || !info.Explicit {
|
|
t.Fatalf("null metadata = %#v, %v", info, ok)
|
|
}
|
|
}
|
|
|
|
func TestFacadeMappingImportsUseExplicitContext(t *testing.T) {
|
|
namespace := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"root/shared.yml": &fstest.MapFile{Data: []byte("name: imported\n")},
|
|
})
|
|
source := configkit.Mapping("programmatic", map[string]any{
|
|
"configkit": map[string]any{"imports": []any{map[string]any{"path": "shared.yml"}}},
|
|
"enabled": true,
|
|
})
|
|
result, err := configkit.Load(source, configkit.Options[mappingFacadeConfig]{
|
|
Imports: true, Filesystem: &configkit.FilesystemContext{Root: "root", BaseDir: "root", FS: &namespace},
|
|
})
|
|
if err != nil || result.Config.Name != "imported" || !result.Config.Enabled {
|
|
t.Fatalf("mapping import = %#v, %v", result, err)
|
|
}
|
|
if sources := result.Sources(); len(sources) != 2 || sources[0].Kind != configkit.SourceKindMapping || sources[1].Kind != configkit.SourceKindFilesystem || sources[1].Layer != configkit.LayerBase {
|
|
t.Fatalf("mapping import sources = %#v", sources)
|
|
}
|
|
checkMappingCode(t, source, configkit.Options[mappingFacadeConfig]{Imports: true}, configkit.CodeOptionsInvalid)
|
|
checkMappingCode(t, source, configkit.Options[mappingFacadeConfig]{}, configkit.CodeImportDisabled)
|
|
}
|
|
|
|
func TestFacadeMappingDeferredValueDiagnostics(t *testing.T) {
|
|
cycle := map[string]any{}
|
|
cycle["secret"] = cycle
|
|
for _, test := range []struct {
|
|
name string
|
|
source configkit.Source
|
|
want configkit.Code
|
|
path string
|
|
}{
|
|
{"nil root", configkit.Mapping("nil", nil), configkit.CodeSourceInvalid, ""},
|
|
{"bad label", configkit.Mapping("bad\nlabel", map[string]any{}), configkit.CodeSourceInvalid, ""},
|
|
{"unsupported", configkit.Mapping("bad", map[string]any{"secret": struct{ Value string }{"canary-value"}}), configkit.CodeSourceInvalid, "/secret"},
|
|
{"nonfinite", configkit.Mapping("bad", map[string]any{"count": math.NaN()}), configkit.CodeSourceInvalid, "/count"},
|
|
{"cycle", configkit.Mapping("bad", cycle), configkit.CodeSourceInvalid, "/secret"},
|
|
{"too many bytes", configkit.Mapping("bad", map[string]any{"name": strings.Repeat("x", 8<<20+1)}), configkit.CodeSourceLimitExceeded, "/name"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
loadError := checkMappingCode(t, test.source, configkit.Options[mappingFacadeConfig]{}, test.want)
|
|
diagnostic := loadError.Diagnostics()[0]
|
|
if diagnostic.Path.String() != test.path || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" || strings.Contains(diagnostic.Message, "canary-value") {
|
|
t.Fatalf("diagnostic = %#v", diagnostic)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkMappingCode(t *testing.T, source configkit.Source, options configkit.Options[mappingFacadeConfig], want configkit.Code) *configkit.LoadError {
|
|
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)
|
|
}
|
|
return loadError
|
|
}
|