Check source versions in selected overlays
This commit is contained in:
@@ -974,6 +974,201 @@ func TestSourceVersionUnderlaysProtectRemainingMountedPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionSelectedOverlayConsumesHeaderAndRetainsWarning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: main-v}\nsettings: {kept: base}\n")
|
||||
overlayPath := writeImportFile(t, directory, "overlays/testing.yml", "schema: {version: overlay-v}\nsettings: {changed: overlay}\n")
|
||||
var checked []string
|
||||
loaded, err := Load(File(mainPath), testSecretBinding(), Options[map[string]any]{
|
||||
Overlay: "testing", SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("schema", "version"),
|
||||
Check: func(version Version, validation *Validation) error {
|
||||
checked = append(checked, version.Text)
|
||||
if version.Text == "overlay-v" {
|
||||
validation.Warning(Path{}, "overlay accepted with advisory")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(checked) != 2 || checked[0] != "main-v" || checked[1] != "overlay-v" {
|
||||
t.Fatalf("checked source declarations = %#v", checked)
|
||||
}
|
||||
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)
|
||||
}
|
||||
diagnostics := loaded.Inspection.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeVersionWarning {
|
||||
t.Fatalf("overlay warning was not retained: %#v", diagnostics)
|
||||
}
|
||||
sources := loaded.Inspection.Sources()
|
||||
if len(sources) != 2 || sources[1].Path != filepath.Clean(overlayPath) || sources[1].Layer != LayerOverlay {
|
||||
t.Fatalf("overlay source records = %#v", sources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionSelectedOverlayRequiresAndChecksItsDeclaration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("required absence", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: main-v}\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: overlay}\n")
|
||||
loaded, err := Load(File(mainPath), testSecretBinding(), Options[map[string]any]{
|
||||
Overlay: "testing", SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil },
|
||||
}},
|
||||
})
|
||||
if err == nil || loaded != nil {
|
||||
t.Fatalf("Load = %#v, %v, want required overlay declaration error", loaded, err)
|
||||
}
|
||||
loadErr, ok := err.(*LoadError)
|
||||
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeVersionError {
|
||||
t.Fatalf("missing overlay diagnostics = %#v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("checker rejection", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: main-v}\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "schema: {version: rejected-overlay}\nsettings: {changed: hidden}\n")
|
||||
var checked []string
|
||||
loaded, err := Load(File(mainPath), testSecretBinding(), Options[map[string]any]{
|
||||
Overlay: "testing", SourceVersionMode: sourceVersionModeIfPresent,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("schema", "version"),
|
||||
Check: func(version Version, _ *Validation) error {
|
||||
checked = append(checked, version.Text)
|
||||
if version.Text == "rejected-overlay" {
|
||||
return errors.New("overlay rejected")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err == nil || loaded != nil {
|
||||
t.Fatalf("Load = %#v, %v, want overlay rejection", loaded, err)
|
||||
}
|
||||
loadErr, ok := err.(*LoadError)
|
||||
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeVersionHookFailed {
|
||||
t.Fatalf("overlay rejection diagnostics = %#v", err)
|
||||
}
|
||||
if len(checked) != 2 || checked[0] != "main-v" || checked[1] != "rejected-overlay" {
|
||||
t.Fatalf("checked source declarations = %#v", checked)
|
||||
}
|
||||
})
|
||||
|
||||
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")
|
||||
checks := 0
|
||||
loaded, err := Load(File(mainPath), testSecretBinding(), Options[map[string]any]{
|
||||
Overlay: "testing", SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("schema", "version"), Check: func(Version, *Validation) error { checks++; return nil },
|
||||
}},
|
||||
})
|
||||
if err == nil || loaded != nil {
|
||||
t.Fatalf("Load = %#v, %v, want structural overlay rejection", loaded, err)
|
||||
}
|
||||
loadErr, ok := err.(*LoadError)
|
||||
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeOverlayDirective {
|
||||
t.Fatalf("structural overlay diagnostics = %#v", err)
|
||||
}
|
||||
if checks != 1 {
|
||||
t.Fatalf("version checker calls = %d, want main only", checks)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSourceVersionOverlayLeavesCLIOverridesHeaderFree(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checks := 0
|
||||
loaded, err := Load(Bytes("main", []byte("schema: {version: main-v}\nsettings: {base: value}\n")), testSecretBinding(), Options[map[string]any]{
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("schema", "version"),
|
||||
Check: func(Version, *Validation) error {
|
||||
checks++
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
Overrides: []Override{{Path: Keys("settings", "cli"), Value: "supplied", Flag: "--settings.cli"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Load with CLI override: %v", err)
|
||||
}
|
||||
if checks != 1 || loaded.Config["settings"].(map[string]any)["cli"] != "supplied" {
|
||||
t.Fatalf("checker count/configuration = %d, %#v", checks, loaded.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionOverlayProtectsMountedMainAndPreservesSequenceCoordinates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("mounted main ownership", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "meta: {version: main-v}\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "meta: {version: overlay-v}\nservice: {meta: {version: payload}}\n")
|
||||
_, err := LoadLayered(Input{Source: File(mainPath), Destination: Keys("service")}, testSecretBinding(), LayeredOptions[map[string]any]{
|
||||
Overlay: "testing", OverlayFilesystem: &FilesystemContext{BaseDir: directory, Root: directory},
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
Definition: Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("meta", "version"), Check: func(Version, *Validation) error { return nil },
|
||||
}},
|
||||
})
|
||||
loadErr, ok := err.(*LoadError)
|
||||
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeOverlayVersion {
|
||||
t.Fatalf("mounted overlay ownership error = %#v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sequence location remains source-local after deletion", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "base: true\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "versions:\n - before\n - overlay-version\n - after\n")
|
||||
main, diagnostics := parseSource(File(mainPath), "source-main", defaultParseLimits())
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("parse main: %#v", diagnostics)
|
||||
}
|
||||
localRule := &VersionRule{Path: Keys("versions").Index(1), Check: func(version Version, _ *Validation) error {
|
||||
if version.Text != "overlay-version" {
|
||||
t.Errorf("checked version = %#v", version)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
effectiveRule := effectiveSourceVersionRule(localRule, Keys("service"), sourceVersionModeRequired)
|
||||
state := &sourceVersionState{localRule: localRule, effectiveRule: effectiveRule, mode: sourceVersionModeRequired}
|
||||
base, compositionDiagnostics := composeDocuments(&document.Node{Kind: document.Mapping})
|
||||
if len(compositionDiagnostics) != 0 {
|
||||
t.Fatalf("compose base: %#v", compositionDiagnostics)
|
||||
}
|
||||
assembly := newAssemblyAccumulator(base)
|
||||
_, diagnostics = loadSelectedOverlayIntoWithVersion(
|
||||
File(mainPath), nil, main, assembly, sourceInfos(main.source), "testing", effectiveRule, defaultImportLimits(), state,
|
||||
)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("load selected overlay: %#v", diagnostics)
|
||||
}
|
||||
if len(state.evidence) != 1 || state.evidence[0].resolvedPath.String() != "/versions/1" || state.evidence[0].location == nil || state.evidence[0].location.Line != 3 {
|
||||
t.Fatalf("sequence evidence lost original coordinates: %#v", state.evidence)
|
||||
}
|
||||
versions, found := documentAtPath(assembly.current.root, Keys("versions"))
|
||||
if !found || versions.Kind != document.Sequence || len(versions.Sequence) != 2 || versions.Sequence[0].Text != "before" || versions.Sequence[1].Text != "after" {
|
||||
t.Fatalf("remaining overlay sequence = %#v", versions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user