Resolve nested inheritance in destination scopes
This commit is contained in:
196
internal/engine/inheritance_walk_test.go
Normal file
196
internal/engine/inheritance_walk_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user