package engine import ( "reflect" "strconv" "strings" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestSiblingInheritanceResolvesReferencesAndKeepsInputs(t *testing.T) { tests := []struct { name, input string path Path text string transfers int controls int }{ { name: "forward reference", input: "child: {extends: parent, value: own}\nparent: {value: parent, kept: preserved}\n", path: Keys("child", "kept"), text: "preserved", transfers: 1, controls: 1, }, { name: "backward reference", input: "parent: {value: parent, kept: preserved}\nchild: {extends: parent, value: own}\n", path: Keys("child", "value"), text: "own", transfers: 1, controls: 1, }, { name: "chain and shared parent", input: "last: {extends: middle, enabled: false}\nfirst: {value: original}\nmiddle: {extends: first, extra: middle}\nother: {extends: first}\n", path: Keys("last", "value"), text: "original", transfers: 3, controls: 3, }, { name: "exact empty sibling name", input: "child: {extends: \"\"}\n\"\": {value: empty}\n", path: Keys("child", "value"), text: "empty", transfers: 1, controls: 1, }, { name: "exact unusual sibling name", input: "child: {extends: \"a/b~c\"}\n\"a/b~c\": {value: unusual}\n", path: Keys("child", "value"), text: "unusual", transfers: 1, controls: 1, }, { name: "null cancellation", input: "child: {extends: null, value: own}\nparent: {value: parent}\n", path: Keys("child", "value"), text: "own", controls: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { input := parseCompositionDocument(t, "source-1", test.input) before := document.Clone(input) result, diagnostics := expandSiblingDefinitions(input, Path{}) if len(diagnostics) != 0 { t.Fatalf("diagnostics = %#v", diagnostics) } if len(result.transfers) != test.transfers || len(result.controls) != test.controls { t.Fatalf("evidence = %#v, %#v", result.transfers, result.controls) } node, found := documentAtPath(result.root, test.path) if !found || node.Text != test.text { t.Fatalf("%s = %#v, %v", test.path.String(), node, found) } if _, found := documentAtPath(result.root, Keys("child", inheritanceKey)); found { t.Fatal("child directive remained in expanded payload") } if !reflect.DeepEqual(input, before) { t.Fatal("resolver changed input") } node.Text = "changed" if !reflect.DeepEqual(input, before) { t.Fatal("expanded payload aliases input") } }) } } func TestSiblingInheritanceTracksTransferAndIndependentChildren(t *testing.T) { input := parseCompositionDocument(t, "source-1", "first: {value: parent}\nsecond: {extends: first}\nthird: {extends: first}\n") result, diagnostics := expandSiblingDefinitions(input, Keys("catalog")) if len(diagnostics) != 0 { t.Fatal(diagnostics) } if len(result.transfers) != 2 || result.transfers[0].from.String() != "/catalog/first" || result.transfers[0].to.String() != "/catalog/second" || result.transfers[0].reference.String() != "/catalog/second/extends" || len(result.transfers[0].locations) != 1 || result.transfers[0].locations[0].SourceID != "source-1" { t.Fatalf("transfers = %#v", result.transfers) } second, _ := documentAtPath(result.root, Keys("second", "value")) third, _ := documentAtPath(result.root, Keys("third", "value")) second.Text = "changed" if third.Text != "parent" { t.Fatal("siblings share mutable expanded values") } } func TestSiblingInheritanceRejectsInvalidRelationships(t *testing.T) { tests := []struct { name, input, path string code Code related bool }{ {"missing", "child: {extends: private-reference-canary}\n", "/child/extends", CodeInheritanceMissing, false}, {"nonmapping parent", "child: {extends: parent}\nparent: null\n", "/child/extends", CodeInheritanceInvalid, true}, {"boolean reference", "child: {extends: true}\n", "/child/extends", CodeInheritanceInvalid, false}, {"sequence reference", "child: {extends: [parent]}\n", "/child/extends", CodeInheritanceInvalid, false}, {"self reference", "child: {extends: child}\n", "/child/extends", CodeInheritanceCycle, true}, {"longer cycle", "a: {extends: b}\nb: {extends: c}\nc: {extends: a}\n", "/c/extends", CodeInheritanceCycle, true}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { input := parseCompositionDocument(t, "source-1", test.input) result, diagnostics := expandSiblingDefinitions(input, Path{}) if result != nil || len(diagnostics) != 1 { t.Fatalf("result and diagnostics = %#v, %#v", result, diagnostics) } problem := diagnostics[0] if problem.Code != test.code || problem.Path.String() != test.path || problem.Primary == nil || problem.Primary.SourceID != "source-1" || problem.Primary.Line < 1 || (len(problem.Related) != 0) != test.related || strings.Contains(problem.Message, "private-reference-canary") { t.Fatalf("diagnostic = %#v", problem) } }) } } func TestLoadDetectsInvalidInheritanceInsideSequence(t *testing.T) { input := []byte(`items: - child: {extends: true} `) result, err := testLoad[map[string]any](Bytes("nested-inheritance.yml", input), Options[map[string]any]{}) if result != nil || err == nil { t.Fatalf("result, err = %#v, %v; want invalid nested inheritance", result, err) } loadError, ok := err.(*LoadError) if !ok { t.Fatalf("error type = %T, want *LoadError", err) } diagnostics := loadError.Diagnostics() if len(diagnostics) != 1 || diagnostics[0].Code != CodeInheritanceInvalid || diagnostics[0].Path.String() != "/items/0/child/extends" { t.Fatalf("diagnostics = %#v", diagnostics) } } func TestSiblingInheritanceDetectsDeepCycleWithoutRecursiveDependencyWalk(t *testing.T) { const count = 2048 entries := make([]document.Entry, count) for index := range entries { location := document.Location{SourceID: "source-1", Line: index + 1, Column: 1} parent := strconv.Itoa((index + 1) % count) entries[index] = document.Entry{ Key: strconv.Itoa(index), Value: &document.Node{Kind: document.Mapping, Location: location, Mapping: []document.Entry{{Key: inheritanceKey, Value: &document.Node{ Kind: document.String, Text: parent, Location: location, }}}}, } } result, diagnostics := expandSiblingDefinitions(&document.Node{Kind: document.Mapping, Mapping: entries}, Path{}) if result != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeInheritanceCycle { t.Fatalf("deep cycle result = %#v, diagnostics = %#v", result, diagnostics) } }