Resolve nested inheritance in destination scopes

This commit is contained in:
2026-09-29 22:31:07 +00:00
parent fdbc306cfb
commit 4207110e2f
4 changed files with 478 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
package engine
import (
"reflect"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestNestedInheritanceUsesEachDestinationScope(t *testing.T) {
input := parseCompositionDocument(t, "source-1", `presets:
base:
settings:
primary: {width: 10}
secondary: {extends: primary}
custom:
extends: base
settings:
primary: {width: 20}
`)
before := document.Clone(input)
result, diagnostics := expandInheritanceTree(input, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
for path, want := range map[string]string{
"/presets/base/settings/secondary/width": "10",
"/presets/custom/settings/secondary/width": "20",
} {
parsed, _ := ParsePath(path)
node, found := documentAtPath(result.root, parsed)
if !found || node.Text != want {
t.Errorf("%s = %#v, found %v", path, node, found)
}
}
if len(result.transfers) != 3 || !reflect.DeepEqual(input, before) {
t.Fatalf("transfers = %#v; input changed = %v", result.transfers, !reflect.DeepEqual(input, before))
}
if node, found := documentAtPath(result.root, Keys("presets", "custom", "settings", "secondary", inheritanceKey)); found {
t.Fatalf("nested directive survived: %#v", node)
}
}
func TestNestedInheritanceSupportsChainsCancellationAndContainerReplacement(t *testing.T) {
input := parseCompositionDocument(t, "source-1", `catalog:
alpha:
settings:
first: {value: one}
second: {extends: first}
beta:
extends: alpha
settings:
first: {value: two}
gamma:
extends: beta
settings:
first: {value: three}
cancelled:
extends: alpha
settings:
second: {extends: null, value: own}
replaced:
extends: alpha
settings: none
`)
result, diagnostics := expandInheritanceTree(input, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
checks := map[string]string{
"/catalog/alpha/settings/second/value": "one",
"/catalog/beta/settings/second/value": "two",
"/catalog/gamma/settings/second/value": "three",
"/catalog/cancelled/settings/second/value": "own",
"/catalog/replaced/settings": "none",
}
for pointer, want := range checks {
path, _ := ParsePath(pointer)
node, found := documentAtPath(result.root, path)
if !found || node.Text != want {
t.Errorf("%s = %#v, found %v", pointer, node, found)
}
}
if _, found := documentAtPath(result.root, Keys("catalog", "replaced", "settings", "second")); found {
t.Fatal("replaced container retained an active child")
}
if _, found := documentAtPath(result.root, Keys("catalog", "cancelled", "settings", "second", inheritanceKey)); found {
t.Fatal("cancelled nested directive survived")
}
}
func TestNestedInheritanceCanReplaceAnInheritedRelationship(t *testing.T) {
input := parseCompositionDocument(t, "source-1", `base:
settings:
first: {value: old}
other: {value: new}
selected: {extends: first}
custom:
extends: base
settings:
selected: {extends: other}
`)
result, diagnostics := expandInheritanceTree(input, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
base, found := documentAtPath(result.root, Keys("base", "settings", "selected", "value"))
if !found || base.Text != "old" {
t.Fatalf("base relationship = %#v, %v", base, found)
}
custom, found := documentAtPath(result.root, Keys("custom", "settings", "selected", "value"))
if !found || custom.Text != "new" {
t.Fatalf("replaced relationship = %#v, %v", custom, found)
}
}
func TestNestedInheritanceRejectsDestinationCycleAndInvalidPositions(t *testing.T) {
tests := []struct {
name, input, path string
code Code
}{
{
name: "destination-only cycle", input: `base:
settings:
a: {value: one}
b: {extends: a}
custom:
extends: base
settings:
a: {extends: b}
`, path: "/custom/settings/b/extends", code: CodeInheritanceCycle,
},
{"root reference", "extends: anything\nentry: {value: one}\n", "/extends", CodeInheritanceInvalid},
{"list element reference", "items: [{extends: anything, value: one}]\n", "/items/0/extends", CodeInheritanceInvalid},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
input := parseCompositionDocument(t, "source-1", test.input)
result, diagnostics := expandInheritanceTree(input, defaultProjectionLimits())
if result != nil || len(diagnostics) != 1 || diagnostics[0].Code != test.code ||
diagnostics[0].Path.String() != test.path || diagnostics[0].Primary == nil {
t.Fatalf("result = %#v, diagnostics = %#v", result, diagnostics)
}
})
}
}
func TestNestedInheritanceResolvesScopesInsideSequences(t *testing.T) {
input := parseCompositionDocument(t, "source-1", `items:
- primary: {value: first}
secondary: {extends: primary}
- extends: null
zeta: {extends: alpha}
alpha: {value: second}
`)
result, diagnostics := expandInheritanceTree(input, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
first, found := documentAtPath(result.root, Keys("items").Index(0).Key("secondary").Key("value"))
if !found || first.Text != "first" {
t.Fatalf("first sequence scope = %#v, %v", first, found)
}
second, found := documentAtPath(result.root, Keys("items").Index(1).Key("zeta").Key("value"))
if !found || second.Text != "second" {
t.Fatalf("second sequence scope = %#v, %v", second, found)
}
if _, found := documentAtPath(result.root, Keys("items").Index(1).Key(inheritanceKey)); found {
t.Fatal("sequence-element cancellation survived")
}
}
func TestInheritanceExpansionHonorsOutputNodeAndDepthBudgets(t *testing.T) {
input := parseCompositionDocument(t, "source-1", "base: {value: one}\nchild: {extends: base}\n")
for _, test := range []struct {
name string
limits projectionLimits
valid bool
}{
{name: "at node boundary", limits: projectionLimits{nodes: 9, depth: 2}, valid: true},
{name: "beyond node boundary", limits: projectionLimits{nodes: 8, depth: 2}},
{name: "at depth boundary", limits: projectionLimits{nodes: 20, depth: 2}, valid: true},
{name: "beyond depth boundary", limits: projectionLimits{nodes: 20, depth: 1}},
} {
t.Run(test.name, func(t *testing.T) {
result, diagnostics := expandInheritanceTree(input, test.limits)
if test.valid {
if result == nil || len(diagnostics) != 0 {
t.Fatalf("result = %#v, diagnostics = %#v", result, diagnostics)
}
} else if result != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeSchemaProjectionLimit {
t.Fatalf("result = %#v, diagnostics = %#v", result, diagnostics)
}
})
}
}