Files
ConfigKit/internal/engine/load_request_test.go

577 lines
27 KiB
Go

package engine
import (
"reflect"
"strings"
"testing"
"testing/fstest"
)
type groupedConfig struct {
Version string `yaml:"version"`
Settings map[string]string `yaml:"settings"`
Port int `yaml:"port"`
Derived string `yaml:"derived"`
}
func testGroupedLoad[T any](request loadRequest[T]) (*testResult[T], error) {
loaded, err := loadGroupedWithSecretLookup(request, testSecretBinding(), func(string) (string, bool) { return "", false })
return testLoaded(loaded), err
}
func TestUnderlayGroupSchedulesInterleavedImportBranches(t *testing.T) {
directory := t.TempDir()
zRoot := writeImportFile(t, directory, "z-root.yml", `configkit:
imports:
- path: d-parent.yml
items: [{id: z}]
`)
dParent := writeImportFile(t, directory, "d-parent.yml", `configkit:
imports:
- path: a-dependency.yml
items: [{id: d}]
`)
aDependency := writeImportFile(t, directory, "a-dependency.yml", "items: [{id: a}]\n")
bRoot := writeImportFile(t, directory, "b-root.yml", `configkit:
imports:
- path: c-helper.yml
items: [{id: b}]
`)
cHelper := writeImportFile(t, directory, "c-helper.yml", "items: [{id: c}]\n")
request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{
{source: NoSource()},
{source: File(zRoot), imports: true},
{source: File(bRoot), imports: true},
}}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
}
result, err := testGroupedLoad(request)
if err != nil {
t.Fatalf("grouped load: %v", err)
}
items := result.Config["items"].([]any)
wantIDs := []string{"a", "c", "b", "d", "z"}
if len(items) != len(wantIDs) {
t.Fatalf("scheduled items = %#v", items)
}
for index, want := range wantIDs {
if got := items[index].(map[string]any)["id"]; got != want {
t.Errorf("scheduled item %d ID = %#v, want %q", index, got, want)
}
}
wantPaths := []string{zRoot, dParent, aDependency, bRoot, cHelper}
for index, want := range wantPaths {
if got := result.Sources()[index].Path; got != canonicalTestPath(t, want) {
t.Errorf("acquired source %d path = %q, want %q", index, got, canonicalTestPath(t, want))
}
}
if got := result.Sources(); len(got) != len(wantPaths) {
t.Fatalf("NoSource added a source record: %#v", got)
}
}
func TestUnderlayGroupAcquisitionRetainsScopesUntilComposition(t *testing.T) {
filesystem := NewFilesystem("handoff", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: child.yml}]}\nmain: retained\n")},
"child.yml": &fstest.MapFile{Data: []byte("child: retained\n")},
})
accounting := &importAccounting{}
group := loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}
acquired, diagnostics := acquireUnderlayGroupScopes(group, 2, nil, accounting, nil, sequencePolicy{})
if len(diagnostics) != 0 {
t.Fatalf("acquire underlay group: %#v", diagnostics)
}
if acquired == nil || acquired.index != 2 || len(acquired.scopes) != 1 || len(acquired.scopes[0].contributions) != 2 {
t.Fatalf("acquisition handoff lost its source scope: %#v", acquired)
}
if len(acquired.sources) != 2 || acquired.sources[0].UnderlayIndex != 2 || acquired.sources[1].ID != "source-2" {
t.Fatalf("acquired source evidence = %#v", acquired.sources)
}
if acquired.inputBytes != int64(len("configkit: {imports: [{path: child.yml}]}\nmain: retained\n")+len("child: retained\n")) {
t.Fatalf("group input byte accounting = %d", acquired.inputBytes)
}
composition, diagnostics := composeUnderlayGroup(acquired, nil, sequencePolicy{}, collectionPolicyScope{})
if len(diagnostics) != 0 {
t.Fatalf("compose acquired group: %#v", diagnostics)
}
for key, want := range map[string]string{"main": "retained", "child": "retained"} {
value, present := documentAtPath(composition.root, Keys(key))
if !present || value.Text != want {
t.Errorf("composed %s = %#v, %v; want %q", key, value, present, want)
}
}
if composition.inputBytes != acquired.inputBytes || len(composition.acquisition) != 1 || len(composition.acquisition[0].contributions) != 2 {
t.Fatalf("composed group handoff = %#v", composition)
}
empty, diagnostics := acquireUnderlayGroupScopes(loadGroup{}, 3, nil, accounting, nil, sequencePolicy{})
if len(diagnostics) != 0 || len(empty.scopes) != 0 {
t.Fatalf("empty group acquisition = %#v, %#v", empty, diagnostics)
}
if composition, diagnostics := composeUnderlayGroup(empty, nil, sequencePolicy{}, collectionPolicyScope{}); len(diagnostics) != 0 || composition != nil {
t.Fatalf("empty group composition = %#v, %#v", composition, diagnostics)
}
}
func TestUnderlayNamespaceAnchorsUseInputPositionsAndFilesystemIdentity(t *testing.T) {
firstNamespace := NewFilesystem("same-label", fstest.MapFS{
"root.yml": &fstest.MapFile{Data: []byte("items: [{id: first}]\n")},
"later.yml": &fstest.MapFile{Data: []byte("items: [{id: copied}]\n")},
})
secondNamespace := NewFilesystem("same-label", fstest.MapFS{
"root.yml": &fstest.MapFile{Data: []byte("items: [{id: second}]\n")},
})
request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{
{source: firstNamespace.File("root.yml")},
{source: secondNamespace.File("root.yml")},
{source: firstNamespace.File("later.yml")},
}}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
}
result, err := testGroupedLoad(request)
if err != nil {
t.Fatalf("grouped load with native namespaces: %v", err)
}
items := result.Config["items"].([]any)
wantIDs := []string{"copied", "first", "second"}
if len(items) != len(wantIDs) {
t.Fatalf("scheduled native items = %#v", items)
}
for index, want := range wantIDs {
if got := items[index].(map[string]any)["id"]; got != want {
t.Errorf("scheduled native item %d ID = %#v, want %q", index, got, want)
}
}
if sources := result.Sources(); len(sources) != len(wantIDs) {
t.Fatalf("source records = %#v", sources)
}
}
func TestUnderlayItemConflictUsesScheduledGroupIndex(t *testing.T) {
directory := t.TempDir()
zItem := writeImportFile(t, directory, "z-item.yml", "id: duplicate\n")
aItem := writeImportFile(t, directory, "a-item.yml", "id: duplicate\n")
request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{
{source: File(zItem), destination: Keys("items"), placementMode: placementModeSequenceItem},
{source: File(aItem), destination: Keys("items"), placementMode: placementModeSequenceItem},
}}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
}
_, err := testGroupedLoad(request)
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) != 1 {
t.Fatalf("expected item conflict: %v", err)
}
diagnostic := loadError.Diagnostics()[0]
if diagnostic.Code != CodeComposeConflict || diagnostic.Path.String() != "/items" {
t.Fatalf("group item conflict = %s %s, want compose.conflict /items", diagnostic.Code, diagnostic.Path.String())
}
files := make(map[string]string)
for _, source := range loadError.Sources() {
files[source.ID] = source.Path
}
locations := append([]Location(nil), diagnostic.Related...)
if diagnostic.Primary != nil {
locations = append(locations, *diagnostic.Primary)
}
wantLocations := map[string]Location{
canonicalTestPath(t, zItem): {Line: 1, Column: 5},
canonicalTestPath(t, aItem): {Line: 1, Column: 5},
}
gotPaths := make(map[string]bool, len(locations))
for _, location := range locations {
want := wantLocations[files[location.SourceID]]
gotPaths[files[location.SourceID]] = location.Line == want.Line && location.Column == want.Column
}
if len(locations) != 2 || !gotPaths[canonicalTestPath(t, zItem)] || !gotPaths[canonicalTestPath(t, aItem)] {
t.Fatalf("group item conflict sites = %#v; source paths = %#v", locations, files)
}
}
func TestUnderlayItemHelperConflictUsesScheduledGroupIndex(t *testing.T) {
directory := t.TempDir()
zBad := writeImportFile(t, directory, "z-bad.yml", "configkit: {imports: [{path: helper.yml}]}\nid: duplicate\n")
helper := writeImportFile(t, directory, "helper.yml", "id: duplicate\n")
aGood := writeImportFile(t, directory, "a-good.yml", "id: good\n")
request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{{inputs: []loadInput{
{source: File(zBad), imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem},
{source: File(aGood), destination: Keys("items"), placementMode: placementModeSequenceItem},
}}},
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
}
_, err := testGroupedLoad(request)
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) != 1 {
t.Fatalf("expected item helper conflict: %v", err)
}
diagnostic := loadError.Diagnostics()[0]
if diagnostic.Code != CodeComposeConflict || diagnostic.Path.String() != "/items/1/id" {
t.Fatalf("group item helper conflict = %s %s, want compose.conflict /items/1/id", diagnostic.Code, diagnostic.Path.String())
}
files := make(map[string]string)
for _, source := range loadError.Sources() {
files[source.ID] = source.Path
}
if len(files) != 3 {
t.Fatalf("group acquisition stopped before all sources were read: %#v", files)
}
locations := append([]Location(nil), diagnostic.Related...)
if diagnostic.Primary != nil {
locations = append(locations, *diagnostic.Primary)
}
wantLocations := map[string]Location{
canonicalTestPath(t, zBad): {Line: 2, Column: 5},
canonicalTestPath(t, helper): {Line: 1, Column: 5},
}
gotPaths := make(map[string]bool, len(locations))
for _, location := range locations {
want := wantLocations[files[location.SourceID]]
gotPaths[files[location.SourceID]] = location.Line == want.Line && location.Column == want.Column
}
if len(locations) != 2 || !gotPaths[canonicalTestPath(t, zBad)] || !gotPaths[canonicalTestPath(t, helper)] {
t.Fatalf("group item helper conflict sites = %#v; source paths = %#v", locations, files)
}
}
func TestGroupedLoadPrecedenceSourceOrderAndSingleLifecycle(t *testing.T) {
filesystem := NewFilesystem("bundle", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: imported.yml}]}\nversion: v1\nport: 5\nsettings: {shared: main}\n")},
"imported.yml": &fstest.MapFile{Data: []byte("settings: {imported: included}\n")},
"overlays/test.yml": &fstest.MapFile{Data: []byte("settings: {shared: overlay}\n")},
})
checks := struct{ version, defaults, resolution, validation int }{}
request := loadRequest[groupedConfig]{
main: loadInput{source: filesystem.File("main.yml"), imports: true},
underlays: []loadGroup{
{inputs: []loadInput{
{source: Mapping("low-a", map[string]any{"settings": map[string]any{"shared": "low", "a": "one"}, "port": "invalid until replaced"})},
{source: Bytes("low-b", []byte("settings: {b: two}\n"))},
}},
{inputs: []loadInput{{source: Mapping("middle", map[string]any{"settings": map[string]any{"shared": "middle", "c": "three"}})}}},
},
definition: Definition[groupedConfig]{
Version: &VersionRule{Path: Keys("version"), Check: func(version Version, validation *Validation) error {
checks.version++
if version.Text != "v1" {
t.Errorf("version = %#v", version)
}
return nil
}},
Defaults: func() groupedConfig { checks.defaults++; return groupedConfig{Derived: "initial"} },
Resolve: func(config *groupedConfig, resolution *Resolution) error {
checks.resolution++
Default(resolution, Keys("derived"), &config.Derived, "resolved", "set derived")
return nil
},
Validate: func(config *groupedConfig, validation *Validation) { checks.validation++ },
},
overlay: "test", overlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem},
overrides: []Override{{Path: Keys("settings", "shared"), Value: "cli", Flag: "--shared"}},
}
result, err := testGroupedLoad(request)
if err != nil {
t.Fatalf("load: %v, diagnostics %#v", err, err.(*LoadError).Diagnostics())
}
wantSettings := map[string]string{"shared": "cli", "a": "one", "b": "two", "c": "three", "imported": "included"}
if result.Config.Port != 5 || !reflect.DeepEqual(result.Config.Settings, wantSettings) {
t.Fatalf("config = %#v", result.Config)
}
if checks != (struct{ version, defaults, resolution, validation int }{1, 1, 1, 1}) {
t.Fatalf("lifecycle calls = %#v", checks)
}
sources := result.Sources()
if len(sources) != 7 {
t.Fatalf("sources = %#v", sources)
}
wantLayers := []Layer{LayerBase, LayerBase, LayerUnderlay, LayerUnderlay, LayerUnderlay, LayerOverlay, LayerCLI}
wantIndexes := []int{0, 0, 1, 1, 2, 0, 0}
for index, source := range sources {
if source.ID != "source-"+string(rune('1'+index)) || source.Layer != wantLayers[index] || source.UnderlayIndex != wantIndexes[index] {
t.Errorf("source %d = %#v", index, source)
}
}
info, ok := result.Metadata().Lookup(Keys("settings", "shared"))
if !ok || info.Origin.Layer != LayerCLI {
t.Fatalf("effective shared metadata = %#v, %v", info, ok)
}
explanation, ok := result.Explain(Keys("settings", "shared"))
if !ok || len(explanation.History) < 5 {
t.Fatalf("shared explanation = %#v, %v", explanation, ok)
}
var layers []Layer
for _, change := range explanation.History {
if change.Origin.Layer != LayerDefault && (len(layers) == 0 || layers[len(layers)-1] != change.Origin.Layer) {
layers = append(layers, change.Origin.Layer)
}
}
if !reflect.DeepEqual(layers, []Layer{LayerUnderlay, LayerBase, LayerOverlay, LayerCLI}) {
t.Fatalf("history layers = %#v", layers)
}
}
func TestGroupedLoadAbsentMainAndVersionProtection(t *testing.T) {
var observed Version
request := loadRequest[groupedConfig]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{{}, {inputs: []loadInput{{source: Bytes("underlay", []byte("port: 7\n"))}}}},
definition: Definition[groupedConfig]{Version: &VersionRule{Path: Keys("version"), Check: func(version Version, validation *Validation) error {
observed = version
return nil
}}},
}
result, err := testGroupedLoad(request)
if err != nil || result.Config.Port != 7 || observed.Present || len(result.Sources()) != 1 || result.Sources()[0].UnderlayIndex != 2 {
t.Fatalf("underlay-only load = %#v, version %#v, %v", result, observed, err)
}
request.underlays[1].inputs[0].source = Bytes("underlay", []byte("version: forbidden\n"))
_, err = testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeUnderlayVersion {
t.Fatalf("underlay version code = %s", got)
}
filesystem := NewFilesystem("bundle", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: fragment.yml}]}\n")},
"fragment.yml": &fstest.MapFile{Data: []byte("version: forbidden\n")},
})
request.underlays[1].inputs[0] = loadInput{source: filesystem.File("main.yml"), imports: true}
_, err = testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeImportVersion {
t.Fatalf("import version code = %s", got)
}
request.underlays = []loadGroup{{}}
request.definition.Defaults = func() groupedConfig { return groupedConfig{Port: 11} }
result, err = testGroupedLoad(request)
if err != nil || result.Config.Port != 11 || len(result.Sources()) != 0 {
t.Fatalf("defaults-only load = %#v, %v", result, err)
}
}
func TestLayeredCollectionPolicyFlowsBetweenTiers(t *testing.T) {
t.Parallel()
policyOnly := Bytes("lower-policy", []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: append\n"))
duplicate := func(name string) loadInput {
return loadInput{source: Bytes(name, []byte("items: [{id: repeated}]\n"))}
}
request := loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{
{inputs: []loadInput{{source: policyOnly}}},
{inputs: []loadInput{duplicate("first-peer"), duplicate("second-peer")}},
},
}
if _, err := testGroupedLoad(request); err != nil && groupedErrorCode(t, err) != CodeComposeConflict {
loadErr, _ := err.(*LoadError)
t.Fatalf("higher strict group code = %s, want %s: %#v", groupedErrorCode(t, err), CodeComposeConflict, loadErr.Diagnostics())
} else if err == nil {
t.Fatal("higher strict group accepted duplicate inherited identities")
}
request = loadRequest[map[string]any]{
main: loadInput{source: NoSource()},
underlays: []loadGroup{
{inputs: []loadInput{{source: Bytes("lower", []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: append\nitems: [{id: repeated}]\n"))}}},
{inputs: []loadInput{duplicate("higher")}},
},
}
if _, err := testGroupedLoad(request); err == nil || groupedErrorCode(t, err) != CodeSequenceConflict {
t.Fatalf("higher tier did not inherit explicit append mode: %v", err)
}
request = loadRequest[map[string]any]{
main: loadInput{source: Bytes("main", []byte("configkit:\n sequences:\n - destination: [items]\n key: name\n mode: replace\nitems: [{name: higher}]\n"))},
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: append\nitems: [{id: lower}]\n"))}}}},
}
result, err := testGroupedLoad(request)
if err != nil || !reflect.DeepEqual(result.Config["items"], []any{map[string]any{"name": "higher"}}) {
var diagnostics []Diagnostic
if loadErr, ok := err.(*LoadError); ok {
diagnostics = loadErr.Diagnostics()
}
t.Fatalf("replacement did not retire lower identity: %#v, %v (%#v)", result, err, diagnostics)
}
filesystem := NewFilesystem("overlay", fstest.MapFS{
"overlays/testing.yml": &fstest.MapFile{Data: []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: append\nitems: [{id: overlay}]\n")},
})
definition := Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}}
request = loadRequest[map[string]any]{
main: loadInput{source: Bytes("main", []byte("items: [{id: main}]\n"))},
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("items: [{id: lower}]\n"))}}}},
definition: definition, sequencePolicy: mustCompileSequenceIdentityPolicy(t, definition.sequenceIdentityRegistrations),
overlay: "testing", overlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem},
}
result, err = testGroupedLoad(request)
if err != nil || !reflect.DeepEqual(result.Config["items"], []any{
map[string]any{"id": "lower"}, map[string]any{"id": "main"}, map[string]any{"id": "overlay"},
}) {
t.Fatalf("layered selected overlay sequence policy = %#v, %v", result, err)
}
}
func TestGroupedLoadStrictConflictsRepetitionAndBudgets(t *testing.T) {
main := loadInput{source: Bytes("main", []byte("port: 5\n"))}
request := loadRequest[groupedConfig]{
main: main,
underlays: []loadGroup{{inputs: []loadInput{
{source: Bytes("left", []byte("port: 1\n"))},
{source: Bytes("right", []byte("port: 2\n"))},
}}},
}
_, err := testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeComposeConflict {
t.Fatalf("strict conflict code = %s", got)
}
conflicting := NewFilesystem("bundle", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: fragment.yml}]}\nport: 1\n")},
"fragment.yml": &fstest.MapFile{Data: []byte("port: 2\n")},
})
request.main = loadInput{source: conflicting.File("main.yml"), imports: true}
request.underlays = []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("port: 3\n"))}}}}
_, err = testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeComposeConflict {
t.Fatalf("main/import conflict code = %s", got)
}
request.main = main
filesystem := NewFilesystem("bundle", fstest.MapFS{"same.yml": &fstest.MapFile{Data: []byte("port: 1\n")}})
file := filesystem.File("same.yml")
request.underlays = []loadGroup{{inputs: []loadInput{{source: file}, {source: file}}}}
_, err = testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeImportRepeated {
t.Fatalf("same-group repetition code = %s", got)
}
request.underlays = []loadGroup{{inputs: []loadInput{{source: file}}}, {inputs: []loadInput{{source: file}}}}
if result, err := testGroupedLoad(request); err != nil || result.Config.Port != 5 || len(result.Sources()) != 3 {
t.Fatalf("cross-group reuse = %#v, %v", result, err)
}
accounting := &importAccounting{sources: maxImportSources - 1}
group := loadGroup{inputs: []loadInput{{source: Mapping("first", map[string]any{})}, {source: Mapping("second", map[string]any{})}}}
_, _, _, diagnostics := testAcquireUnderlayGroup(group, 1, nil, accounting)
if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded {
t.Fatalf("shared source budget = %#v", diagnostics)
}
accounting = &importAccounting{bytes: maxTotalBytes - 2}
group.inputs = []loadInput{{source: Mapping("first", map[string]any{"port": "one"})}}
_, _, _, diagnostics = testAcquireUnderlayGroup(group, 1, nil, accounting)
if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded {
t.Fatalf("shared byte budget = %#v", diagnostics)
}
}
func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) {
acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, accounting, nil, sequencePolicy{})
if len(diagnostics) != 0 {
return nil, acquired.sources, acquired.contexts, diagnostics
}
composition, diagnostics := composeUnderlayGroup(acquired, rule, sequencePolicy{}, collectionPolicyScope{})
return composition, acquired.sources, acquired.contexts, diagnostics
}
func TestGroupedLoadInvalidIntermediateTypeCanBeReplaced(t *testing.T) {
request := loadRequest[groupedConfig]{
main: loadInput{source: Bytes("main", []byte("port: 9\n"))},
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("low", []byte("port: wrong\n"))}}}},
}
result, err := testGroupedLoad(request)
if err != nil || result.Config.Port != 9 {
t.Fatalf("final typed decode = %#v, %v", result, err)
}
request.main.source = Bytes("main", []byte("port: wrong\n"))
_, err = testGroupedLoad(request)
if got := groupedErrorCode(t, err); got != CodeDecodeType && got != CodeDecodeRange {
t.Fatalf("final invalid type code = %s", got)
}
}
func groupedErrorCode(t *testing.T, err error) Code {
t.Helper()
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) == 0 {
t.Fatalf("error = %#v", err)
}
if strings.Contains(loadError.Diagnostics()[0].Message, "wrong") {
t.Fatal("raw value escaped diagnostics")
}
return loadError.Diagnostics()[0].Code
}
func TestGroupedAccountingChargesMixedOccurrencesOnce(t *testing.T) {
const (
one = "configkit: {imports: [{path: child-one.yml}]}\nleft: first\n"
child = "extra: imported\n"
two = "configkit: {imports: [{path: child-two.yml}]}\nright: second\n"
)
filesystem := NewFilesystem("bundle", fstest.MapFS{
"one.yml": &fstest.MapFile{Data: []byte(one)},
"child-one.yml": &fstest.MapFile{Data: []byte(child)},
"two.yml": &fstest.MapFile{Data: []byte(two)},
"child-two.yml": &fstest.MapFile{Data: []byte(child)},
})
accounting := &importAccounting{}
first, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("one.yml"), imports: true}}}, 1, nil, accounting)
if len(diagnostics) != 0 || first == nil || len(sources) != 2 || accounting.sources != 2 || accounting.bytes != int64(len(one)+len(child)) {
t.Fatalf("first acquisition = %#v, %#v, %#v, accounting %#v", first, sources, diagnostics, accounting)
}
second, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("two.yml"), imports: true}}}, 2, nil, accounting)
if len(diagnostics) != 0 || second == nil || len(sources) != 2 || sources[0].ID != "source-3" || sources[1].ID != "source-4" || accounting.sources != 4 || accounting.bytes != int64(len(one)+len(two)+2*len(child)) {
t.Fatalf("second acquisition = %#v, %#v, %#v, accounting %#v", second, sources, diagnostics, accounting)
}
var wantNodes int
for _, text := range []string{one, child, two, child} {
parsed, diagnostics := parseSource(Bytes("fixture", []byte(text)), "source-1", defaultParseLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
wantNodes += countInputNodes(parsed.document)
}
if accounting.nodes != wantNodes {
t.Fatalf("charged nodes = %d, want %d", accounting.nodes, wantNodes)
}
if first.inputBytes != int64(len(one)+len(child)) || second.inputBytes != int64(len(two)+len(child)) {
t.Fatalf("composition costs = %d, %d, accounting %d", first.inputBytes, second.inputBytes, accounting.bytes)
}
}
func TestGroupedGlobEntryBudgetIsShared(t *testing.T) {
filesystem := NewFilesystem("bundle", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{glob: 'parts/*.yml'}]}\n")},
"parts/a.yml": &fstest.MapFile{Data: []byte("left: one\n")},
"parts/b.yml": &fstest.MapFile{Data: []byte("right: two\n")},
})
accounting := &importAccounting{entries: maxGlobEntries - 1}
_, _, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}, 1, nil, accounting)
if len(diagnostics) == 0 || diagnostics[0].Code != CodeImportLimitExceeded || accounting.entries <= maxGlobEntries {
t.Fatalf("shared glob budget = %#v, accounting %#v", diagnostics, accounting)
}
}
func TestGroupedMixedSourcesShareCLIRecordLimit(t *testing.T) {
filesystem := NewFilesystem("bundle", fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte("value: base\n")}})
inputs := make([]Input, maxImportSources-1)
inputs[0] = Input{Source: Bytes("empty-bytes", []byte("{}\n"))}
for index := 1; index < len(inputs); index++ {
inputs[index] = Input{Source: Mapping("empty-map", map[string]any{})}
}
type config struct {
Value string `yaml:"value"`
}
_, err := LoadLayered(Input{Source: filesystem.File("main.yml")}, testSecretBinding(), LayeredOptions[config]{
Underlays: []Underlay{{Inputs: inputs}},
Overrides: []Override{{Path: Keys("value"), Value: "cli", Flag: "--value"}},
})
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeOverrideLimitExceeded || len(loadError.Sources()) != maxImportSources {
t.Fatalf("mixed source/CLI limit = %#v", err)
}
}