package configkit_test import ( "encoding/json" "errors" "fmt" "reflect" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit/internal/facade" ) var ( _ error = (*configkit.LoadError)(nil) _ fmt.Formatter = configkit.Secret{} _ fmt.Stringer = configkit.Path{} _ json.Marshaler = configkit.Secret{} ) func TestFacadeValuesRemainRootOwned(t *testing.T) { t.Parallel() const packagePath = "gitea.maximumdirect.net/eric/ConfigKit/internal/facade" for _, candidate := range []reflect.Type{ reflect.TypeFor[configkit.Path](), reflect.TypeFor[configkit.Source](), reflect.TypeFor[configkit.Secret](), reflect.TypeFor[configkit.Diagnostic](), reflect.TypeFor[configkit.LoadError](), reflect.TypeFor[configkit.Metadata](), reflect.TypeFor[configkit.Resolution](), reflect.TypeFor[configkit.Validation](), reflect.TypeFor[configkit.Version](), reflect.TypeFor[configkit.VersionRule](), reflect.TypeFor[configkit.FilesystemContext](), reflect.TypeFor[configkit.Override](), reflect.TypeFor[configkit.Definition[struct{}]](), reflect.TypeFor[configkit.Options[struct{}]](), reflect.TypeFor[configkit.Result[struct{}]](), } { if candidate.PkgPath() != packagePath { t.Fatalf("type %s belongs to %q", candidate, candidate.PkgPath()) } } secret := configkit.SecretEnv("SAFE_NAME") formatted := fmt.Sprintf("%#v", secret) encoded, err := json.Marshal(secret) if formatted == "" || err != nil || string(encoded) != `"SAFE_NAME"` { t.Fatalf("secret rendering = %q, %s, %v", formatted, encoded, err) } if got := configkit.Keys("a/b~").Index(0).String(); got != "/a~1b~0/0" { t.Fatalf("path = %q", got) } if got := (&configkit.LoadError{}).Error(); got != "configkit: configuration load failed" { t.Fatalf("zero LoadError = %q", got) } } func TestFacadeLoadBridgesLifecycleCallbacks(t *testing.T) { t.Setenv("FACADE_SECRET_NAME", "credential") type configuration struct { Version string `yaml:"version"` Input int `yaml:"input"` Derived int `yaml:"derived"` Secret configkit.Secret `yaml:"secret"` Labels map[string]string `yaml:"labels"` } var retainedResolution *configkit.Resolution var retainedValidation *configkit.Validation versionCalls, defaultsCalls, resolveCalls, validateCalls := 0, 0, 0, 0 definition := configkit.Definition[configuration]{ Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(version configkit.Version, validation *configkit.Validation) error { versionCalls++ retainedValidation = validation if !version.Present || version.Text != "1" { validation.Error(configkit.Keys("version"), "unsupported version") } return nil }}, Defaults: func() configuration { defaultsCalls++ return configuration{Derived: 1, Labels: map[string]string{"default": "kept"}} }, Resolve: func(configuration *configuration, resolution *configkit.Resolution) error { resolveCalls++ retainedResolution = resolution if !resolution.IsExplicit(configkit.Keys("input")) { resolution.Error(configkit.Keys("input"), "input is required") } input, ok := resolution.InputInfo(configkit.Keys("input")) if !ok || !input.Explicit { resolution.Error(configkit.Keys("input"), "missing input provenance") } source, ok := resolution.Source("source-1") if !ok || source.Label != "facade.yml" { resolution.Error(configkit.Keys("input"), "missing source") } configkit.Default(resolution, configkit.Keys("derived"), &configuration.Derived, configuration.Input*2, "derived from input", configkit.Keys("input")) return nil }, Validate: func(configuration *configuration, validation *configkit.Validation) { validateCalls++ retainedValidation = validation if !configuration.Secret.IsResolved() || configuration.Secret.Value() != "credential" { validation.Error(configkit.Keys("secret"), "secret did not resolve") } }, } if versionCalls != 0 || defaultsCalls != 0 || resolveCalls != 0 || validateCalls != 0 { t.Fatal("callbacks ran while constructing options") } result, err := configkit.Load(configkit.Bytes("facade.yml", []byte("version: 1\ninput: 3\nsecret: FACADE_SECRET_NAME\n")), configkit.Options[configuration]{Definition: definition}) if err != nil { t.Fatal(err) } if versionCalls != 1 || defaultsCalls != 1 || resolveCalls != 1 || validateCalls != 1 || result.Config.Derived != 6 { t.Fatalf("callback counts/configuration = %d/%d/%d/%d %#v", versionCalls, defaultsCalls, resolveCalls, validateCalls, result.Config) } derived, ok := result.Metadata().Lookup(configkit.Keys("derived")) if !ok || derived.Explicit || derived.Origin.Operation != configkit.ChangeDerivedDefault { t.Fatalf("derived metadata = %#v, %v", derived, ok) } if sources := result.Sources(); len(sources) != 1 || sources[0].Label != "facade.yml" { t.Fatalf("sources = %#v", sources) } if result.Diagnostics() != nil { t.Fatalf("diagnostics = %#v", result.Diagnostics()) } for _, context := range []func(){ func() { retainedResolution.IsExplicit(configkit.Keys("input")) }, func() { retainedValidation.Warning(configkit.Keys("input"), "too late") }, } { func() { defer func() { if recover() == nil { t.Fatal("retained callback context remained active") } }() context() }() } } func TestFacadeLoadReturnsFacadeLoadError(t *testing.T) { t.Parallel() type configuration struct { Value int `yaml:"value"` } result, err := configkit.Load[configuration](configkit.Bytes("invalid.yml", []byte("value: wrong\n"))) if result != nil || err == nil { t.Fatalf("result, err = %#v, %v", result, err) } var loadError *configkit.LoadError if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeDecodeType { t.Fatalf("facade error = %#v", err) } }