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"` } 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 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 }