package engine import ( "reflect" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestInheritanceGraphSchedulesDirectRelationshipsAndAppliesTemplates(t *testing.T) { root := parseCompositionDocument(t, "schedule", ` child: extends: base own: child-value base: inherited: base-value cleared: extends: null own: cleared-value scalar: kept `) original := document.Clone(root) graph, diagnostics := analyzeInheritanceGraph(root, Path{}) if len(diagnostics) != 0 { t.Fatalf("analyze diagnostics = %#v", diagnostics) } if len(graph.edges) != 1 { t.Fatalf("edges = %#v, want one direct parent edge", graph.edges) } order, diagnostics := graph.schedule() if len(diagnostics) != 0 { t.Fatalf("schedule diagnostics = %#v", diagnostics) } orderNames := make([]string, len(order)) for i, index := range order { orderNames[i] = graph.nodes[index].name } if want := []string{"base", "child", "cleared"}; !reflect.DeepEqual(orderNames, want) { t.Fatalf("mapping schedule = %v, want %v", orderNames, want) } result, diagnostics := applyInheritanceTemplates(graph, order, defaultProjectionLimits(), nil) if len(diagnostics) != 0 { t.Fatalf("apply diagnostics = %#v", diagnostics) } if !reflect.DeepEqual(root, original) { t.Fatal("applying templates mutated the input mapping") } if len(result.controls) != 2 || result.controls[0].path.String() != "/child/extends" || result.controls[0].cancelled || result.controls[1].path.String() != "/cleared/extends" || !result.controls[1].cancelled { t.Fatalf("controls = %#v, want scheduled ordinary and null relationships", result.controls) } if len(result.transfers) != 1 || result.transfers[0].from.String() != "/base" || result.transfers[0].to.String() != "/child" { t.Fatalf("transfers = %#v, want the direct parent transfer", result.transfers) } if got, want := result.root.Mapping[0].Key, "child"; got != want { t.Fatalf("root order begins with %q, want %q", got, want) } child := result.root.Mapping[0].Value inherited, found := documentAtPath(child, Path{}.Key("inherited")) if !found || inherited.Text != "base-value" { t.Fatalf("inherited value = %#v, found %t", inherited, found) } childValue, found := documentAtPath(child, Path{}.Key("own")) if !found || childValue.Text != "child-value" { t.Fatalf("child value = %#v, found %t", childValue, found) } cleared := result.root.Mapping[2].Value clearedValue, found := documentAtPath(cleared, Path{}.Key("own")) if !found || clearedValue.Text != "cleared-value" { t.Fatalf("null relationship value = %#v, found %t", clearedValue, found) } if _, found := inheritanceDirective(cleared); found { t.Fatal("null relationship directive remained in the template") } child.Mapping[0].Value.Text = "changed-result" inherited, found = documentAtPath(root.Mapping[1].Value, Path{}.Key("inherited")) if !found || inherited.Text != "base-value" { t.Fatalf("mutating result changed input: inherited value = %#v, found %t", inherited, found) } } func TestInheritanceGraphReportsCycleAtAuthoredRelationship(t *testing.T) { root := parseCompositionDocument(t, "cycle-source", `alpha: extends: beta beta: extends: alpha `) graph, diagnostics := analyzeInheritanceGraph(root, Path{}) if len(diagnostics) != 0 { t.Fatalf("analyze diagnostics = %#v", diagnostics) } _, diagnostics = graph.schedule() if len(diagnostics) != 1 || diagnostics[0].Code != CodeInheritanceCycle { t.Fatalf("cycle diagnostics = %#v", diagnostics) } diagnostic := diagnostics[0] if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "cycle-source" { t.Fatalf("cycle primary = %#v, want authored source", diagnostic.Primary) } if diagnostic.Primary.Line != 4 { t.Fatalf("cycle primary line = %d, want line 4", diagnostic.Primary.Line) } if len(diagnostic.Related) != 1 || diagnostic.Related[0].SourceID != "cycle-source" { t.Fatalf("cycle related sites = %#v, want the referenced mapping", diagnostic.Related) } if diagnostic.Message != "sibling inheritance contains a cycle" { t.Fatalf("cycle message exposes unexpected details: %q", diagnostic.Message) } } func TestInheritanceGraphValidatesRelationshipKinds(t *testing.T) { tests := []struct { name string text string code Code }{ {name: "missing sibling", text: "child:\n extends: absent\n", code: CodeInheritanceMissing}, {name: "non-mapping sibling", text: "base: scalar\nchild:\n extends: base\n", code: CodeInheritanceInvalid}, {name: "invalid relationship", text: "child:\n extends: 3\n", code: CodeInheritanceInvalid}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { root := parseCompositionDocument(t, "invalid", test.text) _, diagnostics := analyzeInheritanceGraph(root, Path{}) if len(diagnostics) != 1 || diagnostics[0].Code != test.code { t.Fatalf("diagnostics = %#v, want code %q", diagnostics, test.code) } if diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "invalid" { t.Fatalf("primary site = %#v, want authored declaration", diagnostics[0].Primary) } }) } }