|
|
|
|
@@ -24,7 +24,7 @@ func TestDefaultMainVersionWarningSurvivesLayeredLoading(t *testing.T) {
|
|
|
|
|
}{
|
|
|
|
|
{name: "ordinary", ordinary: true},
|
|
|
|
|
{name: "layered", lower: "{}"},
|
|
|
|
|
{name: "later structural failure", body: "configkit:\n sequences: [{destination: [items], key: id}]\nitems: []\n", lower: "items: null", failure: true},
|
|
|
|
|
{name: "later structural failure", body: "config:\n sequences: [{destination: [items], key: id}]\nitems: []\n", lower: "items: null", failure: true},
|
|
|
|
|
{name: "later decoding failure", body: "count: invalid\n", lower: "{}", failure: true},
|
|
|
|
|
} {
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
|
@@ -433,8 +433,8 @@ func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
|
|
|
|
|
wantCode Code
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "inside import declaration", input: "configkit:\n imports:\n - path: child.yml\n",
|
|
|
|
|
path: Keys("configkit", "imports").Index(0).Key("path"), wantCode: CodeVersionError,
|
|
|
|
|
name: "inside import declaration", input: "config:\n imports:\n - path: child.yml\n",
|
|
|
|
|
path: Keys("config", "imports").Index(0).Key("path"), wantCode: CodeVersionError,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "extends control", input: "settings:\n extends: parent\n",
|
|
|
|
|
@@ -445,8 +445,8 @@ func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
|
|
|
|
|
path: Keys("settings", "extends", "version"), wantCode: CodeVersionError,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "malformed directive cannot be consumed", input: "configkit:\n imports: true\n",
|
|
|
|
|
path: Keys("configkit", "imports"), wantCode: CodeImportInvalid,
|
|
|
|
|
name: "malformed directive cannot be consumed", input: "config:\n imports: true\n",
|
|
|
|
|
path: Keys("config", "imports"), wantCode: CodeImportInvalid,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, test := range tests {
|
|
|
|
|
@@ -471,7 +471,7 @@ func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unrelatedRoot := sourceVersionTestDocument(t, "configkit:\n imports:\n - path: child.yml\nmetadata:\n version: 4\n")
|
|
|
|
|
unrelatedRoot := sourceVersionTestDocument(t, "config:\n imports:\n - path: child.yml\nmetadata:\n version: 4\n")
|
|
|
|
|
unrelatedResult := processSourceVersion(unrelatedRoot, &VersionRule{
|
|
|
|
|
Path: Keys("metadata", "version"), Check: func(Version, *Validation) error { return nil },
|
|
|
|
|
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
|
|
|
|
|
@@ -489,6 +489,84 @@ func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSourceVersionAllowsNestedConfigData(t *testing.T) {
|
|
|
|
|
root := sourceVersionTestDocument(t, "service: {config: {version: 4, imports: ordinary}}\n")
|
|
|
|
|
called := false
|
|
|
|
|
result := processSourceVersion(root, &VersionRule{
|
|
|
|
|
Path: Keys("service", "config", "version"),
|
|
|
|
|
Check: func(version Version, _ *Validation) error {
|
|
|
|
|
called = version.Present && version.Text == "4"
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
|
|
|
|
|
if len(result.diagnostics) != 0 || !called || result.payload == nil {
|
|
|
|
|
t.Fatalf("nested version result = %#v, checked = %t", result, called)
|
|
|
|
|
}
|
|
|
|
|
if _, found := documentAtPath(result.payload, Keys("service", "config", "version")); found {
|
|
|
|
|
t.Fatal("accepted contribution header was not consumed")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSourceVersionMountedConfigKeepsLocalAndEffectivePaths(t *testing.T) {
|
|
|
|
|
type config struct {
|
|
|
|
|
Config struct {
|
|
|
|
|
Version string `yaml:"version"`
|
|
|
|
|
} `yaml:"config"`
|
|
|
|
|
}
|
|
|
|
|
checks := 0
|
|
|
|
|
definition := Definition[config]{Version: &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error {
|
|
|
|
|
checks++
|
|
|
|
|
if !version.Present || version.Text != "4" {
|
|
|
|
|
return errors.New("unexpected version")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}}}
|
|
|
|
|
main := Input{Source: Bytes("main", []byte("version: 4\n")), Destination: Keys("config")}
|
|
|
|
|
loaded, err := LoadLayered(main, testSecretBinding(), LayeredOptions[config]{Definition: definition, SourceVersionMode: sourceVersionModeRequired})
|
|
|
|
|
if err != nil || loaded == nil || loaded.Config.Config.Version != "4" || checks != 1 {
|
|
|
|
|
t.Fatalf("mounted version = %#v, checks %d, error %v", loaded, checks, err)
|
|
|
|
|
}
|
|
|
|
|
_, err = LoadLayered(main, testSecretBinding(), LayeredOptions[config]{Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
|
|
|
|
Overrides: []Override{{Path: Keys("config", "version"), Value: "5", Flag: "--version"}},
|
|
|
|
|
})
|
|
|
|
|
var loadErr *LoadError
|
|
|
|
|
if !errors.As(err, &loadErr) {
|
|
|
|
|
t.Fatalf("protected override error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOverrideVersion, "/config/version")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSourceVersionLayeredRejectsControlHeaderBeforeChecking(t *testing.T) {
|
|
|
|
|
called := false
|
|
|
|
|
definition := Definition[map[string]any]{Version: &VersionRule{
|
|
|
|
|
Path: Keys("config", "imports").Index(0).Key("path"),
|
|
|
|
|
Check: func(Version, *Validation) error { called = true; return nil },
|
|
|
|
|
}}
|
|
|
|
|
mainPath := writeImportFile(t, t.TempDir(), "main.yml", "config: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
_, err := LoadLayered(Input{Source: File(mainPath), Imports: true},
|
|
|
|
|
testSecretBinding(), LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent})
|
|
|
|
|
var loadErr *LoadError
|
|
|
|
|
if !errors.As(err, &loadErr) || called {
|
|
|
|
|
t.Fatalf("overlap error = %v, checker called = %t", err, called)
|
|
|
|
|
}
|
|
|
|
|
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionError, "/config/imports/0/path")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSourceVersionDefaultModeChecksAssembledConfigPath(t *testing.T) {
|
|
|
|
|
checked := false
|
|
|
|
|
definition := Definition[map[string]any]{Version: &VersionRule{
|
|
|
|
|
Path: Keys("config", "version"), Check: func(version Version, _ *Validation) error {
|
|
|
|
|
checked = version.Present && version.Text == "4"
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
loaded, err := LoadLayered(Input{Source: Bytes("main", []byte("version: 4\n")), Destination: Keys("config")},
|
|
|
|
|
testSecretBinding(), LayeredOptions[map[string]any]{Definition: definition})
|
|
|
|
|
if err != nil || loaded == nil || !checked {
|
|
|
|
|
t.Fatalf("assembled version = %#v, checked = %t, error = %v", loaded, checked, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSourceVersionDefaultModeDoesNotCheckOrConsume(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
@@ -858,13 +936,13 @@ func TestSourceVersionImportsCheckNestedAndGlobSourcesBeforeComposition(t *testi
|
|
|
|
|
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", `version: main
|
|
|
|
|
configkit:
|
|
|
|
|
config:
|
|
|
|
|
imports:
|
|
|
|
|
- glob: fragments/*.yml
|
|
|
|
|
destination: [groups]
|
|
|
|
|
`)
|
|
|
|
|
firstPath := writeImportFile(t, directory, "fragments/a.yml", `version: first
|
|
|
|
|
configkit:
|
|
|
|
|
config:
|
|
|
|
|
imports:
|
|
|
|
|
- path: nested/child.yml
|
|
|
|
|
destination: [nested]
|
|
|
|
|
@@ -935,7 +1013,7 @@ func TestSourceVersionImportsRejectHiddenAndMissingDeclarations(t *testing.T) {
|
|
|
|
|
t.Run("rejected child is not hidden by the main declaration", func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "version: main\nconfigkit: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "version: main\nconfig: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
writeImportFile(t, directory, "child.yml", "version: rejected\n")
|
|
|
|
|
checks := 0
|
|
|
|
|
_, err := Load(File(mainPath), testSecretBinding(), Options[sourceVersionNullConfig]{
|
|
|
|
|
@@ -963,7 +1041,7 @@ func TestSourceVersionImportsRejectHiddenAndMissingDeclarations(t *testing.T) {
|
|
|
|
|
t.Run("required child omission reports its nearest source location", func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "version: main\nconfigkit: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "version: main\nconfig: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
writeImportFile(t, directory, "child.yml", "name: child\n")
|
|
|
|
|
checks := 0
|
|
|
|
|
_, err := Load(File(mainPath), testSecretBinding(), Options[sourceVersionNullConfig]{
|
|
|
|
|
@@ -986,7 +1064,7 @@ func TestSourceVersionImportsRejectHiddenAndMissingDeclarations(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", `version: main
|
|
|
|
|
configkit:
|
|
|
|
|
config:
|
|
|
|
|
imports:
|
|
|
|
|
- path: missing.yml
|
|
|
|
|
`)
|
|
|
|
|
@@ -1144,7 +1222,7 @@ func TestSourceVersionUnderlayImportsKeepLocalHeadersAndMountedDestinations(t *t
|
|
|
|
|
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
underlayPath := writeImportFile(t, directory, "underlay.yml", `meta: {version: peer-root}
|
|
|
|
|
configkit:
|
|
|
|
|
config:
|
|
|
|
|
imports:
|
|
|
|
|
- path: nested.yml
|
|
|
|
|
destination: [nested]
|
|
|
|
|
@@ -1296,7 +1374,7 @@ func TestSourceVersionSelectedOverlayConsumesHeaderAndRetainsWarning(t *testing.
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: main-v}\nsettings: {kept: base}\nitems: [{id: base}]\n")
|
|
|
|
|
overlayPath := writeImportFile(t, directory, "overlays/testing.yml", `schema: {version: overlay-v}
|
|
|
|
|
configkit:
|
|
|
|
|
config:
|
|
|
|
|
sequences:
|
|
|
|
|
- destination: [items]
|
|
|
|
|
key: id
|
|
|
|
|
@@ -1327,7 +1405,7 @@ items: [{id: overlay}]
|
|
|
|
|
if loaded.Config["schema"].(map[string]any)["version"] != "main-v" || loaded.Config["settings"].(map[string]any)["changed"] != "overlay" {
|
|
|
|
|
t.Fatalf("overlay payload = %#v", loaded.Config)
|
|
|
|
|
}
|
|
|
|
|
if _, found := loaded.Config["configkit"]; found || len(loaded.Config["items"].([]any)) != 2 {
|
|
|
|
|
if _, found := loaded.Config["config"]; found || len(loaded.Config["items"].([]any)) != 2 {
|
|
|
|
|
t.Fatalf("overlay declarations or collection values = %#v", loaded.Config)
|
|
|
|
|
}
|
|
|
|
|
diagnostics := loaded.Inspection.Diagnostics()
|
|
|
|
|
@@ -1395,7 +1473,7 @@ func TestSourceVersionSelectedOverlayRequiresAndChecksItsDeclaration(t *testing.
|
|
|
|
|
t.Run("structural validation precedes declaration processing", func(t *testing.T) {
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: main-v}\n")
|
|
|
|
|
writeImportFile(t, directory, "overlays/testing.yml", "configkit: {imports: true}\nschema: {version: overlay-v}\n")
|
|
|
|
|
writeImportFile(t, directory, "overlays/testing.yml", "config: {imports: true}\nschema: {version: overlay-v}\n")
|
|
|
|
|
checks := 0
|
|
|
|
|
loaded, err := Load(File(mainPath), testSecretBinding(), Options[map[string]any]{
|
|
|
|
|
Overlay: "testing", SourceVersionMode: sourceVersionModeRequired,
|
|
|
|
|
@@ -1513,7 +1591,7 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T)
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
directory := t.TempDir()
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
mainPath := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: child.yml}]}\n")
|
|
|
|
|
childPath := writeImportFile(t, directory, "child.yml", "version: accepted\nname: child\n")
|
|
|
|
|
main := parseImportMain(t, mainPath)
|
|
|
|
|
child, diagnostics := parseSource(File(childPath), "source-child", defaultParseLimits())
|
|
|
|
|
|