Files
ConfigKit/internal/engine/source_version_test.go

1236 lines
51 KiB
Go

package engine
import (
"errors"
"path/filepath"
"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"`
}
type sourceVersionImportGroup struct {
Name string `yaml:"name"`
}
type sourceVersionImportConfig struct {
Service struct {
Version string `yaml:"version"`
Groups map[string]sourceVersionImportGroup `yaml:"groups"`
} `yaml:"service"`
}
func sourceVersionTestDocument(t *testing.T, input string) *document.Node {
t.Helper()
parsed, diagnostics := parseSource(Bytes("source-version.yml", []byte(input)), "source-version", defaultParseLimits())
if len(diagnostics) != 0 {
t.Fatalf("parse diagnostics = %#v", diagnostics)
}
return parsed.document
}
func requireSourceVersionDiagnostic(t *testing.T, diagnostics []Diagnostic, code Code, path string) Diagnostic {
t.Helper()
for _, diagnostic := range diagnostics {
if diagnostic.Code == code && diagnostic.Path.String() == path {
return diagnostic
}
}
t.Fatalf("diagnostics = %#v, want %s at %q", diagnostics, code, path)
return Diagnostic{}
}
func TestSourceVersionExtractionPreservesVersionVocabulary(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want Version
}{
{name: "string", input: "version: \"app.v7\"\n", want: Version{Present: true, Text: "app.v7"}},
{name: "integer", input: "version: 123456789012345678901234567890\n", want: Version{Present: true, Text: "123456789012345678901234567890"}},
{name: "null", input: "version: null\n", want: Version{Present: true, Null: true}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
var checked Version
rule := &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error {
checked = version
return nil
}}
result := processSourceVersion(root, rule, sourceVersionContribution, Keys("collections", "weather"), sourceVersionModeIfPresent)
if len(result.diagnostics) != 0 || !result.evidence.checked || result.evidence.version != test.want || checked != test.want {
t.Fatalf("result = %#v, checked = %#v", result, checked)
}
if _, found := documentAtPath(result.payload, rule.Path); found {
t.Fatal("accepted non-main version remained in the payload")
}
if node, found := documentAtPath(root, rule.Path); !found || node == nil {
t.Fatal("source document was mutated while consuming its version")
}
if result.evidence.destination.String() != "/collections/weather" {
t.Fatalf("evidence destination = %q", result.evidence.destination.String())
}
})
}
}
func TestSourceVersionExtractionHandlesMissingAndMalformedValues(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
mode sourceVersionMode
role sourceVersionRole
wantCode Code
wantPresent bool
wantCallback bool
wantAbsence sourceVersionPathAbsence
}{
{
name: "missing mapping member is optional absence", input: "settings: {}\n",
path: Keys("settings", "version"), mode: sourceVersionModeIfPresent,
role: sourceVersionContribution, wantPresent: false, wantAbsence: sourceVersionMemberMissing,
},
{
name: "missing below scalar is optional absence", input: "settings: scalar\n",
path: Keys("settings", "version"), mode: sourceVersionModeIfPresent,
role: sourceVersionContribution, wantPresent: false, wantAbsence: sourceVersionPathObstructed,
},
{
name: "required absence reports nearest location", input: "settings: scalar\n",
path: Keys("settings", "version"), mode: sourceVersionModeRequired,
role: sourceVersionContribution, wantCode: CodeVersionError, wantAbsence: sourceVersionPathObstructed,
},
{
name: "unavailable sequence index is absence", input: "versions: [1]\n",
path: Keys("versions").Index(3), mode: sourceVersionModeRequired,
role: sourceVersionContribution, wantCode: CodeVersionError, wantAbsence: sourceVersionIndexUnavailable,
},
{
name: "required actual main absence", input: "name: demo\n", path: Keys("version"),
mode: sourceVersionModeRequired, role: sourceVersionMain, wantCode: CodeVersionError, wantAbsence: sourceVersionMemberMissing,
},
{
name: "main optional absence invokes checker", input: "name: demo\n", path: Keys("version"),
mode: sourceVersionModeIfPresent, role: sourceVersionMain, wantCallback: true, wantAbsence: sourceVersionMemberMissing,
},
{name: "boolean", input: "version: true\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "fraction", input: "version: 4.5\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "mapping", input: "version: {}\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
{name: "sequence", input: "version: []\n", path: Keys("version"), mode: sourceVersionModeIfPresent, role: sourceVersionContribution, wantCode: CodeVersionType, wantPresent: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
called := false
rule := &VersionRule{Path: test.path, Check: func(Version, *Validation) error {
called = true
return nil
}}
result := processSourceVersion(root, rule, test.role, Path{}, test.mode)
if called != test.wantCallback {
t.Fatalf("checker called = %v, want %v", called, test.wantCallback)
}
if test.wantCode != "" {
diagnostic := requireSourceVersionDiagnostic(t, result.diagnostics, test.wantCode, test.path.String())
if result.payload != nil {
t.Fatalf("failed processing returned payload %#v", result.payload)
}
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-version" || diagnostic.Primary.Line != 1 {
t.Fatalf("diagnostic location = %#v", diagnostic.Primary)
}
} else if len(result.diagnostics) != 0 {
t.Fatalf("diagnostics = %#v", result.diagnostics)
}
if result.evidence == nil || result.evidence.version.Present != test.wantPresent {
t.Fatalf("evidence = %#v", result.evidence)
}
if result.evidence.absence != test.wantAbsence {
t.Fatalf("absence cause = %v, want %v", result.evidence.absence, test.wantAbsence)
}
if test.wantCode == CodeVersionType && (!result.evidence.redacted || result.evidence.version.Text != "") {
t.Fatalf("malformed value was retained: %#v", result.evidence)
}
if !test.wantCallback && result.evidence.checked {
t.Fatalf("evidence incorrectly records a callback: %#v", result.evidence)
}
})
}
}
func TestSourceVersionExtractionConsumesOnlySelectedNodes(t *testing.T) {
t.Parallel()
destination := Keys("collections", "weather")
root := sourceVersionTestDocument(t, "settings:\n version: 8\n")
originalSettings, _ := documentAtPath(root, Keys("settings"))
result := processSourceVersion(root, &VersionRule{
Path: Keys("settings", "version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, destination, sourceVersionModeIfPresent)
settings, found := documentAtPath(result.payload, Keys("settings"))
if len(result.diagnostics) != 0 || !found || settings.Kind != document.Mapping || len(settings.Mapping) != 0 {
t.Fatalf("payload = %#v, diagnostics = %#v", result.payload, result.diagnostics)
}
if settings.Location != originalSettings.Location {
t.Fatalf("authored ancestor location changed: got %#v, want %#v", settings.Location, originalSettings.Location)
}
if original, found := documentAtPath(root, Keys("settings", "version")); !found || original.Text != "8" {
t.Fatalf("original source changed: %#v, found %v", original, found)
}
if len(result.evidence.ancestors) != 3 || result.evidence.ancestors[1].kind != document.Mapping {
t.Fatalf("ancestor shape = %#v", result.evidence.ancestors)
}
sequenceRoot := sourceVersionTestDocument(t, "versions: [5, 7, 9]\n")
originalVersions, _ := documentAtPath(sequenceRoot, Keys("versions"))
originalLastLocation := originalVersions.Sequence[2].Location
sequencePath, err := ParsePath("/versions/1")
if err != nil {
t.Fatal(err)
}
sequenceResult := processSourceVersion(sequenceRoot, &VersionRule{
Path: sequencePath, Check: func(version Version, _ *Validation) error {
if version.Text != "7" {
t.Fatalf("checked version = %#v", version)
}
return nil
},
}, sourceVersionContribution, Path{}, sourceVersionModeRequired)
versions, found := documentAtPath(sequenceResult.payload, Keys("versions"))
if len(sequenceResult.diagnostics) != 0 || !found || versions.Kind != document.Sequence || len(versions.Sequence) != 2 || versions.Sequence[0].Text != "5" || versions.Sequence[1].Text != "9" {
t.Fatalf("sequence payload = %#v, diagnostics = %#v", sequenceResult.payload, sequenceResult.diagnostics)
}
if versions.Sequence[1].Location != originalLastLocation {
t.Fatalf("remaining sequence location changed: got %#v, want %#v", versions.Sequence[1].Location, originalLastLocation)
}
if len(sequenceResult.evidence.resolvedPath.segments) != 2 || sequenceResult.evidence.resolvedPath.segments[1].kind != pathSegmentIndex || sequenceResult.evidence.resolvedPath.segments[1].index != 1 {
t.Fatalf("original sequence path = %#v", sequenceResult.evidence.resolvedPath)
}
itemRoot := sourceVersionTestDocument(t, "items:\n - name: first\n - version: 12\n")
itemResult := processSourceVersion(itemRoot, &VersionRule{
Path: Keys("items").Index(1).Key("version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
items, found := documentAtPath(itemResult.payload, Keys("items"))
if len(itemResult.diagnostics) != 0 || !found || len(items.Sequence) != 2 || items.Sequence[1].Kind != document.Mapping || len(items.Sequence[1].Mapping) != 0 {
t.Fatalf("sequence mapping payload = %#v, diagnostics = %#v", itemResult.payload, itemResult.diagnostics)
}
emptySequenceRoot := sourceVersionTestDocument(t, "versions: [7]\n")
emptySequenceResult := processSourceVersion(emptySequenceRoot, &VersionRule{
Path: Keys("versions").Index(0), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
emptySequence, found := documentAtPath(emptySequenceResult.payload, Keys("versions"))
if len(emptySequenceResult.diagnostics) != 0 || !found || emptySequence.Kind != document.Sequence || len(emptySequence.Sequence) != 0 {
t.Fatalf("empty sequence payload = %#v, diagnostics = %#v", emptySequenceResult.payload, emptySequenceResult.diagnostics)
}
}
func TestSourceVersionExtractionResolvesContextualPathSegments(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
wantRemainingPath bool
wantRemainingText string
}{
{name: "literal numeric mapping key", input: "versions:\n \"0\": 3\n other: 4\n", path: Keys("versions", "0")},
{name: "parsed numeric mapping key", input: "versions:\n \"0\": 3\n other: 4\n", path: mustParseSourceVersionPath(t, "/versions/0")},
{name: "parsed sequence index", input: "versions: [3, 4]\n", path: mustParseSourceVersionPath(t, "/versions/0"), wantRemainingPath: true, wantRemainingText: "4"},
{name: "explicit sequence index", input: "versions: [3, 4]\n", path: Keys("versions").Index(0), wantRemainingPath: true, wantRemainingText: "4"},
{name: "escaped mapping key", input: "a/b:\n version: 3\n", path: mustParseSourceVersionPath(t, "/a~1b/version")},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
result := processSourceVersion(root, &VersionRule{
Path: test.path, Check: func(version Version, _ *Validation) error {
if version.Text != "3" {
t.Errorf("checked version = %#v", version)
}
return nil
},
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
if len(result.diagnostics) != 0 {
t.Fatalf("diagnostics = %#v", result.diagnostics)
}
remaining, found := documentAtPath(result.payload, test.path)
if found != test.wantRemainingPath || found && remaining.Text != test.wantRemainingText {
t.Fatalf("remaining selected path = %#v, found = %v", remaining, found)
}
if !result.evidence.version.Present || result.evidence.version.Text != "3" {
t.Fatalf("evidence = %#v", result.evidence)
}
})
}
}
func TestSourceVersionExtractionRejectsStructuralOverlap(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
path Path
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: "extends control", input: "settings:\n extends: parent\n",
path: Keys("settings", "extends"), wantCode: CodeVersionError,
},
{
name: "invalid extends subtree", input: "settings:\n extends:\n version: 1\n",
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,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, test.input)
called := false
result := processSourceVersion(root, &VersionRule{Path: test.path, Check: func(Version, *Validation) error {
called = true
return nil
}}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
diagnostic := requireSourceVersionDiagnostic(t, result.diagnostics, test.wantCode, test.path.String())
if called || result.payload != nil {
t.Fatalf("invalid overlap produced payload %#v or called checker %v", result.payload, called)
}
if test.wantCode == CodeVersionError && (result.evidence == nil || !result.evidence.redacted || result.evidence.version.Text != "") {
t.Fatalf("overlap evidence was not conservatively redacted: %#v", result.evidence)
}
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-version" {
t.Fatalf("diagnostic location = %#v", diagnostic.Primary)
}
})
}
unrelatedRoot := sourceVersionTestDocument(t, "configkit:\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)
metadata, found := documentAtPath(unrelatedResult.payload, Keys("metadata"))
if len(unrelatedResult.diagnostics) != 0 || !found || len(metadata.Mapping) != 0 || len(unrelatedResult.imports) != 1 {
t.Fatalf("unrelated directive result = %#v", unrelatedResult)
}
ordinaryRoot := sourceVersionTestDocument(t, "extends_version: 4\n")
ordinaryResult := processSourceVersion(ordinaryRoot, &VersionRule{
Path: Keys("extends_version"), Check: func(Version, *Validation) error { return nil },
}, sourceVersionContribution, Path{}, sourceVersionModeIfPresent)
if len(ordinaryResult.diagnostics) != 0 || ordinaryResult.payload == nil {
t.Fatalf("ordinary extends_version key was rejected: %#v", ordinaryResult)
}
}
func TestSourceVersionDefaultModeDoesNotCheckOrConsume(t *testing.T) {
t.Parallel()
root := sourceVersionTestDocument(t, "version: 2\n")
called := false
result := processSourceVersion(root, &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error {
called = true
return nil
}}, sourceVersionContribution, Path{}, sourceVersionModeDefault)
if len(result.diagnostics) != 0 || called || result.evidence != nil {
t.Fatalf("default result = %#v, checker called = %v", result, called)
}
if node, found := documentAtPath(result.payload, Keys("version")); !found || node.Text != "2" {
t.Fatalf("default payload = %#v", result.payload)
}
}
func TestSourceVersionNoSourceMainRetainsAbsentCallback(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
mode sourceVersionMode
}{
{name: "if present", mode: sourceVersionModeIfPresent},
{name: "required", mode: sourceVersionModeRequired},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
called := false
result := processSourceVersion(nil, &VersionRule{
Path: Keys("version"),
Check: func(version Version, _ *Validation) error {
called = true
if version != (Version{}) {
t.Errorf("NoSource version = %#v", version)
}
return nil
},
}, sourceVersionMain, Path{}, test.mode)
if !called || len(result.diagnostics) != 0 || result.evidence != nil || result.payload != nil {
t.Fatalf("NoSource result = %#v, callback called = %v", result, called)
}
})
}
}
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 TestSourceVersionImportsCheckNestedAndGlobSourcesBeforeComposition(t *testing.T) {
t.Parallel()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `version: main
configkit:
imports:
- glob: fragments/*.yml
destination: [groups]
`)
firstPath := writeImportFile(t, directory, "fragments/a.yml", `version: first
configkit:
imports:
- path: nested/child.yml
destination: [nested]
a: {name: A}
`)
nestedPath := writeImportFile(t, directory, "fragments/nested/child.yml", "version: nested\nname: N\n")
secondPath := writeImportFile(t, directory, "fragments/b.yml", "version: second\nb: {name: B}\n")
checked := make(map[string]int)
loaded, err := LoadLayered(Input{
Source: File(mainPath), Imports: true, Destination: Keys("service"),
}, testSecretBinding(), LayeredOptions[sourceVersionImportConfig]{
SourceVersionMode: sourceVersionModeRequired,
Definition: Definition[sourceVersionImportConfig]{Version: &VersionRule{
Path: Keys("version"),
Check: func(version Version, validation *Validation) error {
checked[version.Text]++
validation.Warning(Path{}, "accepted")
return nil
},
}},
})
if err != nil || loaded == nil {
var loadErr *LoadError
if errors.As(err, &loadErr) {
t.Fatalf("source-local import load = %#v, diagnostics %#v", loaded, loadErr.Diagnostics())
}
t.Fatalf("source-local import load = %#v, %v", loaded, err)
}
if loaded.Config.Service.Version != "main" || loaded.Config.Service.Groups["a"].Name != "A" ||
loaded.Config.Service.Groups["b"].Name != "B" || loaded.Config.Service.Groups["nested"].Name != "N" {
t.Fatalf("mounted source-local imports = %#v", loaded.Config.Service)
}
if len(checked) != 4 || checked["main"] != 1 || checked["first"] != 1 || checked["nested"] != 1 || checked["second"] != 1 {
t.Fatalf("checked declarations = %#v, want each independent source once", checked)
}
warnings := testLoaded(loaded).Diagnostics()
if len(warnings) != 4 {
t.Fatalf("accepted source warnings = %#v, want one per checked source", warnings)
}
for _, warning := range warnings {
if warning.Code != CodeVersionWarning || warning.Severity != SeverityWarning {
t.Fatalf("accepted source warning = %#v", warning)
}
}
wantDestinations := map[string]string{
canonicalTestPath(t, mainPath): "/service",
canonicalTestPath(t, firstPath): "/service/groups",
canonicalTestPath(t, secondPath): "/service/groups",
canonicalTestPath(t, nestedPath): "/service/groups/nested",
}
for _, source := range testLoaded(loaded).Sources() {
want, found := wantDestinations[source.Path]
if !found || source.Destination.String() != want {
t.Errorf("source %q destination = %q, want %q", source.Path, source.Destination.String(), want)
}
delete(wantDestinations, source.Path)
}
if len(wantDestinations) != 0 {
t.Errorf("sources not retained: %#v", wantDestinations)
}
}
func TestSourceVersionImportsRejectHiddenAndMissingDeclarations(t *testing.T) {
t.Parallel()
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")
writeImportFile(t, directory, "child.yml", "version: rejected\n")
checks := 0
_, err := Load(File(mainPath), testSecretBinding(), Options[sourceVersionNullConfig]{
Imports: true, SourceVersionMode: sourceVersionModeIfPresent,
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
Path: Keys("version"), Check: func(version Version, _ *Validation) error {
checks++
if version.Text == "rejected" {
return errors.New("rejected")
}
return nil
},
}},
})
var loadErr *LoadError
if !errors.As(err, &loadErr) {
t.Fatalf("rejected child error = %T %v, want *LoadError", err, err)
}
diagnostic := requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionHookFailed, "/version")
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" || checks != 2 {
t.Fatalf("rejected child diagnostic/callbacks = %#v, %d", diagnostic, checks)
}
})
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")
writeImportFile(t, directory, "child.yml", "name: child\n")
checks := 0
_, err := Load(File(mainPath), testSecretBinding(), Options[sourceVersionNullConfig]{
Imports: true, 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("required child omission = %T %v, want *LoadError", err, err)
}
diagnostic := requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeVersionError, "/version")
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" || diagnostic.Primary.Line != 1 || checks != 1 {
t.Fatalf("required omission diagnostic/callbacks = %#v, %d", diagnostic, checks)
}
})
t.Run("optional missing child remains absent", func(t *testing.T) {
t.Parallel()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `version: main
configkit:
imports:
- path: missing.yml
optional: true
`)
checks := 0
loaded, err := Load(File(mainPath), testSecretBinding(), Options[sourceVersionNullConfig]{
Imports: true, SourceVersionMode: sourceVersionModeRequired,
Definition: Definition[sourceVersionNullConfig]{Version: &VersionRule{
Path: Keys("version"), Check: func(Version, *Validation) error { checks++; return nil },
}},
})
if err != nil || loaded == nil || checks != 1 || len(testLoaded(loaded).Sources()) != 1 {
t.Fatalf("optional missing source = %#v, checks %d, %v", loaded, checks, err)
}
})
}
func TestSourceVersionUnderlaysCheckDistinctMountedDeclarations(t *testing.T) {
t.Parallel()
var checked []string
definition := Definition[map[string]any]{Version: &VersionRule{
Path: Keys("meta", "version"),
Check: func(version Version, validation *Validation) error {
checked = append(checked, version.Text)
if version.Text == "peer-a" || version.Text == "peer-c" {
validation.Warning(Path{}, "accepted with advisory")
}
return nil
},
}}
main := Input{
Source: Bytes("main", []byte("meta: {version: main-v}\nservice: primary\n")),
Destination: Keys("service"),
}
first := Input{
Source: Bytes("peer-a", []byte("meta:\n version: peer-a\nvalues: {first: one}\n")),
Destination: Keys("defaults", "first"),
}
second := Input{
Source: Bytes("peer-b", []byte("meta:\n version: peer-b\nvalues: {second: two}\n")),
Destination: Keys("defaults", "second"),
}
third := Input{
Source: Bytes("peer-c", []byte("meta:\n version: peer-c\nvalues: {third: three}\n")),
Destination: Keys("defaults", "third"),
}
loaded, err := LoadLayered(main, testSecretBinding(), LayeredOptions[map[string]any]{
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
Underlays: []Underlay{{Inputs: []Input{first, second}}, {Inputs: []Input{third}}},
})
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
if len(checked) != 4 {
t.Fatalf("checked declarations = %#v, want main and all underlays", checked)
}
for _, want := range []string{"main-v", "peer-a", "peer-b", "peer-c"} {
found := false
for _, got := range checked {
found = found || got == want
}
if !found {
t.Errorf("checked declarations %#v do not include %q", checked, want)
}
}
if len(loaded.Inspection.Diagnostics()) != 2 || loaded.Inspection.Diagnostics()[0].Code != CodeVersionWarning || loaded.Inspection.Diagnostics()[1].Code != CodeVersionWarning {
t.Fatalf("accepted underlay warning was not retained: %#v", loaded.Inspection.Diagnostics())
}
sources := loaded.Inspection.Sources()
if len(sources) != 4 || sources[1].Layer != LayerUnderlay || sources[1].UnderlayIndex != 1 || sources[2].Layer != LayerUnderlay || sources[2].UnderlayIndex != 1 || sources[3].Layer != LayerUnderlay || sources[3].UnderlayIndex != 2 {
t.Fatalf("underlay source records = %#v", sources)
}
defaults, ok := loaded.Config["defaults"].(map[string]any)
if !ok {
t.Fatalf("defaults = %#v", loaded.Config["defaults"])
}
firstValue, ok := defaults["first"].(map[string]any)
if !ok || firstValue["values"] == nil {
t.Fatalf("first mounted contribution = %#v", defaults["first"])
}
if meta, ok := firstValue["meta"].(map[string]any); !ok || len(meta) != 0 {
t.Fatalf("consumed nested declaration did not preserve its empty ancestor: %#v", firstValue["meta"])
}
}
func TestSourceVersionUnderlaysRejectDeclarationsHiddenByMain(t *testing.T) {
t.Parallel()
var checked []string
loaded, err := LoadLayered(Input{Source: Bytes("main", []byte("meta: {version: main-v}\nservice: primary\n"))}, testSecretBinding(), LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{
Path: Keys("meta", "version"),
Check: func(version Version, _ *Validation) error {
checked = append(checked, version.Text)
if version.Text == "rejected-peer" {
return errors.New("peer declaration is not accepted")
}
return nil
},
}},
SourceVersionMode: sourceVersionModeIfPresent,
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("underlay", []byte("meta: {version: rejected-peer}\nservice: shadowed\n"))}}}},
})
if err == nil || loaded != nil {
t.Fatalf("LoadLayered = %#v, %v, want hidden underlay rejection", loaded, err)
}
loadErr, ok := err.(*LoadError)
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeVersionHookFailed {
t.Fatalf("underlay rejection diagnostics = %#v", err)
}
if len(checked) != 2 || checked[0] != "main-v" || checked[1] != "rejected-peer" {
t.Fatalf("checked declarations = %#v", checked)
}
}
func TestSourceVersionUnderlaysRequireActualHeadersButSkipAbsentSources(t *testing.T) {
t.Parallel()
newOptions := func(underlays []Underlay) LayeredOptions[map[string]any] {
return LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{
Path: Keys("meta", "version"),
Check: func(Version, *Validation) error { return nil },
}},
SourceVersionMode: sourceVersionModeRequired,
Underlays: underlays,
}
}
main := Input{Source: Bytes("main", []byte("meta: {version: main-v}\n"))}
t.Run("required absence", func(t *testing.T) {
loaded, err := LoadLayered(main, testSecretBinding(), newOptions([]Underlay{{Inputs: []Input{{Source: Bytes("missing", []byte("value: present\n"))}}}}))
if err == nil || loaded != nil {
t.Fatalf("LoadLayered = %#v, %v, want missing underlay declaration", loaded, err)
}
loadErr, ok := err.(*LoadError)
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeVersionError {
t.Fatalf("missing underlay diagnostics = %#v", err)
}
})
t.Run("empty group and NoSource", func(t *testing.T) {
loaded, err := LoadLayered(main, testSecretBinding(), newOptions([]Underlay{{}, {Inputs: []Input{{Source: NoSource()}}}}))
if err != nil {
t.Fatalf("LoadLayered with absent contributions: %v", err)
}
if sources := loaded.Inspection.Sources(); len(sources) != 1 {
t.Fatalf("empty groups and NoSource created source records: %#v", sources)
}
})
}
func TestSourceVersionUnderlayImportsKeepLocalHeadersAndMountedDestinations(t *testing.T) {
t.Parallel()
directory := t.TempDir()
underlayPath := writeImportFile(t, directory, "underlay.yml", `meta: {version: peer-root}
configkit:
imports:
- path: nested.yml
destination: [nested]
root: retained
`)
writeImportFile(t, directory, "nested.yml", "meta: {version: peer-nested}\nname: nested\n")
var checked []string
loaded, err := LoadLayered(Input{
Source: Bytes("main", []byte("meta: {version: main-v}\n")), Destination: Keys("service"),
}, testSecretBinding(), LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{
Path: Keys("meta", "version"),
Check: func(version Version, _ *Validation) error {
checked = append(checked, version.Text)
return nil
},
}},
SourceVersionMode: sourceVersionModeRequired,
Underlays: []Underlay{{Inputs: []Input{{
Source: File(underlayPath), Imports: true, Destination: Keys("defaults"),
}}}},
})
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
if len(checked) != 3 {
t.Fatalf("checked source declarations = %#v", checked)
}
for _, want := range []string{"main-v", "peer-root", "peer-nested"} {
found := false
for _, got := range checked {
found = found || got == want
}
if !found {
t.Errorf("checked source declarations %#v do not include %q", checked, want)
}
}
sources := loaded.Inspection.Sources()
if len(sources) != 3 || sources[1].Path != filepath.Clean(underlayPath) || sources[2].Destination.String() != "/defaults/nested" {
t.Fatalf("underlay and nested import records = %#v", sources)
}
if sources[1].Layer != LayerUnderlay || sources[1].UnderlayIndex != 1 || sources[2].Layer != LayerUnderlay || sources[2].UnderlayIndex != 1 {
t.Fatalf("underlay and nested import tiers = %#v", sources)
}
defaults, ok := loaded.Config["defaults"].(map[string]any)
if !ok || defaults["root"] != "retained" {
t.Fatalf("mounted underlay root = %#v", loaded.Config["defaults"])
}
nested, ok := defaults["nested"].(map[string]any)
if !ok || nested["name"] != "nested" {
t.Fatalf("mounted nested import = %#v", defaults["nested"])
}
}
func TestSourceVersionUnderlaysProtectRemainingMountedPayload(t *testing.T) {
t.Parallel()
loaded, err := LoadLayered(Input{
Source: Bytes("main", []byte("meta: {version: main-v}\n")), Destination: Keys("service"),
}, testSecretBinding(), LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{
Path: Keys("meta", "version"), Check: func(Version, *Validation) error { return nil },
}},
SourceVersionMode: sourceVersionModeRequired,
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("underlay", []byte(
"meta: {version: accepted}\nservice: {meta: {version: payload}}\n",
))}}}},
})
if err == nil || loaded != nil {
t.Fatalf("LoadLayered = %#v, %v, want protected remaining payload rejection", loaded, err)
}
loadErr, ok := err.(*LoadError)
if !ok || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeUnderlayVersion {
t.Fatalf("remaining payload diagnostics = %#v", err)
}
}
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()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {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())
if len(diagnostics) != 0 {
t.Fatalf("child parse diagnostics = %#v", diagnostics)
}
rule := &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}
state := &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}
wantNodes := countInputNodes(main.document) + countInputNodes(child.document)
wantBytes := main.bytes + child.bytes
limits := defaultImportLimits()
limits.bytes = wantBytes
limits.nodes = wantNodes
accounting := &importAccounting{}
composed, sources, foundDiagnostics := loadImportedDocumentsTrackedWithVersion(
File(mainPath), nil, main, Path{}, rule, limits, accounting, nil, nil, state,
)
if len(foundDiagnostics) != 0 || composed == nil || len(sources) != 2 || accounting.nodes != wantNodes || accounting.bytes != wantBytes {
t.Fatalf("exact raw byte/node accounting = %#v, %d/%d, want %d/%d, %#v", composed, accounting.bytes, accounting.nodes, wantBytes, wantNodes, foundDiagnostics)
}
if len(state.evidence) != 1 || state.evidence[0].sourceID != "source-2" || !state.evidence[0].checked || state.evidence[0].version.Text != "accepted" {
t.Fatalf("acquired child evidence = %#v", state.evidence)
}
if _, present := documentAtPath(composed.root, Keys("version")); present {
t.Fatalf("consumed child header remains in application document: %#v", composed.root)
}
below := defaultImportLimits()
below.nodes = wantNodes - 1
accounting = &importAccounting{}
composed, _, foundDiagnostics = loadImportedDocumentsTrackedWithVersion(
File(mainPath), nil, main, Path{}, rule, below, accounting, nil, nil,
&sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired},
)
if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("raw header nodes were not charged: %#v, %#v", composed, foundDiagnostics)
}
below = defaultImportLimits()
below.bytes = wantBytes - 1
accounting = &importAccounting{}
composed, _, foundDiagnostics = loadImportedDocumentsTrackedWithVersion(
File(mainPath), nil, main, Path{}, rule, below, accounting, nil, nil,
&sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired},
)
if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("raw header bytes were not charged: %#v, %#v", composed, foundDiagnostics)
}
}
func mustParseSourceVersionPath(t *testing.T, input string) Path {
t.Helper()
path, err := ParsePath(input)
if err != nil {
t.Fatal(err)
}
return path
}