Integrate source version checks for main inputs
This commit is contained in:
@@ -1,11 +1,24 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
)
|
||||
|
||||
type sourceVersionMountedService struct {
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
type sourceVersionMountedConfig struct {
|
||||
Service sourceVersionMountedService `yaml:"service"`
|
||||
}
|
||||
|
||||
type sourceVersionNullConfig struct {
|
||||
Version *string `yaml:"version"`
|
||||
}
|
||||
|
||||
func sourceVersionTestDocument(t *testing.T, input string) *document.Node {
|
||||
t.Helper()
|
||||
parsed, diagnostics := parseSource(Bytes("source-version.yml", []byte(input)), "source-version", defaultParseLimits())
|
||||
@@ -374,6 +387,211 @@ func TestSourceVersionNoSourceMainRetainsAbsentCallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadChecksMountedMainLocallyAndProtectsEffectivePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checks := 0
|
||||
definition := Definition[sourceVersionMountedConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"),
|
||||
Check: func(version Version, validation *Validation) error {
|
||||
checks++
|
||||
if !version.Present || version.Null || version.Text != "9007199254740993" {
|
||||
return errors.New("unexpected source-local version")
|
||||
}
|
||||
validation.Warning(Path{}, "advisory")
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
main := Input{Source: Bytes("main.yml", []byte("version: 9007199254740993\n")), Destination: Keys("service")}
|
||||
loaded, err := LoadLayered(main, testSecretBinding(), LayeredOptions[sourceVersionMountedConfig]{
|
||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||
})
|
||||
if err != nil || loaded == nil {
|
||||
t.Fatalf("mounted main load = %#v, %v", loaded, err)
|
||||
}
|
||||
if loaded.Config.Service.Version != "9007199254740993" || checks != 1 {
|
||||
t.Fatalf("mounted main config/callbacks = %#v, %d", loaded.Config, checks)
|
||||
}
|
||||
warnings := testLoaded(loaded).Diagnostics()
|
||||
if len(warnings) != 1 || warnings[0].Code != CodeVersionWarning || warnings[0].Severity != SeverityWarning {
|
||||
t.Fatalf("mounted main warnings = %#v", warnings)
|
||||
}
|
||||
|
||||
checks = 0
|
||||
_, err = LoadLayered(main, testSecretBinding(), LayeredOptions[sourceVersionMountedConfig]{
|
||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||
Overrides: []Override{{Path: Keys("service").Key("version"), Value: "4", Flag: "--version"}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("mounted protected override error = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOverrideVersion, "/service/version")
|
||||
if checks != 1 {
|
||||
t.Fatalf("mounted protected override callbacks = %d, want 1", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadChecksNullAndNoSourceMain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("required actual main absence fails before the checker", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checks := 0
|
||||
_, err := Load(Bytes("main.yml", []byte("other: value\n")), testSecretBinding(), Options[sourceVersionNullConfig]{
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"), Check: func(Version, *Validation) error { checks++; return nil },
|
||||
}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("missing required main version = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionError, "/version")
|
||||
if checks != 0 {
|
||||
t.Fatalf("missing required main invoked checker %d times", checks)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("required null is passed to the checker", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checks := 0
|
||||
loaded, err := Load(Bytes("main.yml", []byte("version: null\n")), testSecretBinding(), Options[sourceVersionNullConfig]{
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"),
|
||||
Check: func(version Version, _ *Validation) error {
|
||||
checks++
|
||||
if !version.Present || !version.Null || version.Text != "" {
|
||||
return errors.New("unexpected null version")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil || loaded == nil || checks != 1 || loaded.Config.Version != nil {
|
||||
t.Fatalf("required null load = %#v, callbacks %d, %v", loaded, checks, err)
|
||||
}
|
||||
})
|
||||
|
||||
for _, mode := range []sourceVersionMode{sourceVersionModeIfPresent, sourceVersionModeRequired} {
|
||||
mode := mode
|
||||
t.Run(map[sourceVersionMode]string{sourceVersionModeIfPresent: "if-present", sourceVersionModeRequired: "required"}[mode], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checks := 0
|
||||
loaded, err := Load(NoSource(), testSecretBinding(), Options[sourceVersionNullConfig]{
|
||||
SourceVersionMode: mode,
|
||||
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"), Check: func(version Version, _ *Validation) error {
|
||||
checks++
|
||||
if version.Present || version.Null {
|
||||
return errors.New("NoSource should report absence")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil || loaded == nil || checks != 1 || len(testLoaded(loaded).Sources()) != 0 {
|
||||
t.Fatalf("NoSource load = %#v, callbacks %d, %v", loaded, checks, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadReportsMountedMainErrorsAtLocalPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checks := 0
|
||||
_, err := LoadLayered(Input{
|
||||
Source: Bytes("main.yml", []byte("version: 5\n")), Destination: Keys("service"),
|
||||
}, testSecretBinding(), LayeredOptions[sourceVersionMountedConfig]{
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[sourceVersionMountedConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"),
|
||||
Check: func(Version, *Validation) error { checks++; return errors.New("rejected") },
|
||||
}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("mounted main rejection = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionHookFailed, "/version")
|
||||
if checks != 1 {
|
||||
t.Fatalf("mounted main checker calls = %d, want 1", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadChecksMainBeforeLayeredAcquisition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checks := 0
|
||||
_, err := LoadLayered(Input{Source: Bytes("main.yml", []byte("version: 5\n"))}, testSecretBinding(), LayeredOptions[sourceVersionNullConfig]{
|
||||
SourceVersionMode: sourceVersionModeIfPresent,
|
||||
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"), Check: func(Version, *Validation) error { checks++; return errors.New("rejected") },
|
||||
}},
|
||||
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("unread.yml", []byte("[malformed"))}}}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("main rejection = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionHookFailed, "/version")
|
||||
if checks != 1 {
|
||||
t.Fatalf("main checker calls = %d, want 1", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionEnabledBuildsSchemaBeforeMainCallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checks := 0
|
||||
_, err := Load(Bytes("main.yml", []byte("version: 5\n")), testSecretBinding(), Options[int]{
|
||||
SourceVersionMode: sourceVersionModeIfPresent,
|
||||
Definition: Definition[int]{Version: &VersionRule{
|
||||
Path: Keys("version"), Check: func(Version, *Validation) error { checks++; return nil },
|
||||
}},
|
||||
})
|
||||
if err == nil || checks != 0 {
|
||||
t.Fatalf("invalid schema load = %v, checker calls %d; want schema failure before callback", err, checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadRejectsOptionsBeforeReadingMain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkerCalls := 0
|
||||
definition := Definition[sourceVersionNullConfig]{Version: &VersionRule{
|
||||
Path: Keys("version"), Check: func(Version, *Validation) error { checkerCalls++; return nil },
|
||||
}}
|
||||
_, err := Load(Bytes("malformed.yml", []byte("[not valid")), testSecretBinding(), Options[sourceVersionNullConfig]{
|
||||
Definition: definition, SourceVersionMode: sourceVersionMode(255),
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("invalid-mode error = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOptionsInvalid, "")
|
||||
if checkerCalls != 0 {
|
||||
t.Fatalf("invalid mode invoked checker %d times", checkerCalls)
|
||||
}
|
||||
|
||||
_, err = Load(Bytes("malformed.yml", []byte("[not valid")), testSecretBinding(), Options[sourceVersionNullConfig]{SourceVersionMode: sourceVersionModeRequired})
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("missing-rule error = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOptionsInvalid, "")
|
||||
|
||||
_, err = LoadLayered(Input{Source: Bytes("malformed.yml", []byte("[not valid"))}, testSecretBinding(), LayeredOptions[sourceVersionNullConfig]{
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
})
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("layered missing-rule error = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOptionsInvalid, "")
|
||||
}
|
||||
|
||||
func mustParseSourceVersionPath(t *testing.T, input string) Path {
|
||||
t.Helper()
|
||||
path, err := ParsePath(input)
|
||||
|
||||
Reference in New Issue
Block a user