package engine import ( "reflect" "slices" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestCoalescedResolutionSnapshotsMatchOrderedScan(t *testing.T) { parsedDependency := parseOverrideTestPath(t, "/dependencies/item") snapshots := []resolutionSnapshot{ {change: Change{Path: Keys("a", "b"), Reasons: []string{"child", "shared", "child"}, Dependencies: []Path{Keys("dependencies", "item")}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Keys("a", "c"), Reasons: []string{"other"}, Dependencies: []Path{Keys("other")}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Keys("a"), Reasons: []string{"parent"}, Dependencies: []Path{Keys("parent")}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Keys("a", "b", "grand"), Reasons: []string{"deep", "shared"}, Dependencies: []Path{parsedDependency, Keys("deep")}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Path{}, Reasons: []string{"root"}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Keys("outside"), Reasons: []string{"root child"}, Origin: Origin{Operation: ChangeDerivedDefault}}}, {change: Change{Path: Keys("a", "b", "normalization"), Reasons: []string{"normalized"}, Origin: Origin{Operation: ChangeNormalization}}}, {change: Change{Path: Keys("a", "none"), Reasons: []string{"parent child"}, Origin: Origin{Operation: ChangeDerivedDefault}}}, } want := coalescedResolutionSnapshotsByScan(snapshots) got := coalescedResolutionSnapshots(snapshots) if !reflect.DeepEqual(got, want) { t.Fatalf("coalesced snapshots differ from ordered scan:\n got: %#v\nwant: %#v", got, want) } if len(got) != 4 || got[0].change.Origin.Operation != ChangeNormalization || !reflect.DeepEqual(got[0].change.Reasons, []string{"child", "shared", "child", "deep", "normalized"}) || len(got[0].change.Dependencies) != 2 || got[3].change.Path.String() != "" { t.Fatalf("coalesced ordering or unions changed: %#v", got) } } func coalescedResolutionSnapshotsByScan(snapshots []resolutionSnapshot) []resolutionSnapshot { groups := make([]resolutionSnapshot, 0, len(snapshots)) for _, snapshot := range snapshots { merged := false for i := range groups { if !pathContainsByPointer(groups[i].change.Path, snapshot.change.Path) { continue } for _, reason := range snapshot.change.Reasons { groups[i].change.Reasons = appendUniqueStringByScan(groups[i].change.Reasons, reason) } for _, dependency := range snapshot.change.Dependencies { groups[i].change.Dependencies = appendUniquePathByPointer(groups[i].change.Dependencies, dependency) } if snapshot.change.Origin.Operation == ChangeNormalization { groups[i].change.Origin.Operation = ChangeNormalization } merged = true break } if !merged { snapshot.change = cloneChange(snapshot.change) groups = append(groups, snapshot) } } return groups } func appendUniqueStringByScan(values []string, value string) []string { if !slices.Contains(values, value) { return append(values, value) } return values } func appendUniquePathByPointer(paths []Path, path Path) []Path { for _, existing := range paths { if existing.String() == path.String() { return paths } } return append(paths, path.clone()) } func TestResolutionDependencyMembershipMatchesPointerSpelling(t *testing.T) { parse := func(pointer string) Path { path, err := ParsePath(pointer) if err != nil { t.Fatalf("ParsePath(%q) error = %v", pointer, err) } return path } paths := []Path{ Keys("a/b"), parse("/a~1b"), Keys("a~1b"), parse("/a~01b"), Keys("items").Index(0), parse("/items/0"), Keys("items", "0"), Keys("items", "01"), parse("/items/01"), Keys("-1"), parse("/-1"), Keys("a", "b"), Keys("a/b"), } var record resolutionRecord var want []Path for _, path := range paths { record.addDependency(path) want = appendUniquePathByPointer(want, path) } if !reflect.DeepEqual(record.dependencies, want) { t.Fatalf("resolution dependencies = %#v, want %#v", record.dependencies, want) } } func TestResolutionCoverageIndexMatchesOrderedScan(t *testing.T) { paths := []Path{ Keys("group"), Keys("group", "field"), Keys("group", "field"), Keys("group", "field", "nested"), Keys("other"), } records := make([]*resolutionRecord, len(paths)) snapshots := make([]resolutionSnapshot, len(paths)) for id, path := range paths { records[id] = &resolutionRecord{path: path} snapshots[id] = resolutionSnapshot{change: Change{Path: path}} } snapshotIndex, pointers := newResolutionCoverageIndexFromSnapshots(snapshots) for id, pointer := range pointers { if pointer != paths[id].String() { t.Errorf("snapshot pointer %d = %q, want %q", id, pointer, paths[id].String()) } } queries := []Path{ Path{}, Keys("group"), Keys("group", "field"), Keys("group", "field", "nested", "leaf"), Keys("group", "missing"), Keys("other", "child"), Keys("group").Index(-1), } for _, test := range []struct { name string index *resolutionCoverageIndex }{ {"records", newResolutionCoverageIndexFromRecords(records)}, {"snapshots", snapshotIndex}, } { t.Run(test.name, func(t *testing.T) { for _, query := range queries { wantID, wantDepth, wantFound := 0, -1, false for id, candidate := range paths { if pathContainsByPointer(candidate, query) && (!wantFound || len(candidate.segments) > wantDepth) { wantID, wantDepth, wantFound = id, len(candidate.segments), true } } gotID, gotFound := test.index.deepestCoveringID(query) if gotID != wantID || gotFound != wantFound { t.Errorf("deepestCoveringID(%s) = %d, %v; want %d, %v", query.String(), gotID, gotFound, wantID, wantFound) } } }) } } func TestDocumentLookupMatchesPathScan(t *testing.T) { name := &document.Node{Kind: document.String, Text: "item"} sequence := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{ {Kind: document.String, Text: "zero"}, {Kind: document.Mapping, Mapping: []document.Entry{{Key: "name", Value: name}}}, }} root := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{ {Key: "0", Value: &document.Node{Kind: document.String, Text: "numeric key"}}, {Key: "01", Value: &document.Node{Kind: document.String, Text: "leading zero key"}}, {Key: "items", Value: sequence}, }} applicationRoot := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{ {Key: "shared", Value: sequence}, {Key: "application", Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "value", Value: &document.Node{Kind: document.String, Text: "application"}}}}}, }} lookup := buildDocumentLookup(root, applicationRoot) paths := []Path{ Path{}, Keys("0"), Keys("01"), Keys("items"), Keys("items").Index(0), Keys("items").Index(1).Key("name"), Keys("items").Index(2), Keys("items").Key("name"), Keys("missing"), Keys("items").Index(-1), (Path{}).Index(0), } for _, path := range paths { wantNode, wantFound := documentAtPath(root, path) gotNode, gotFound := documentAtPathWithLookup(root, path, lookup) if gotNode != wantNode || gotFound != wantFound { t.Errorf("indexed lookup %s = (%p, %v), want (%p, %v)", path.String(), gotNode, gotFound, wantNode, wantFound) } } for _, path := range []Path{Path{}, Keys("shared"), Keys("shared").Index(1).Key("name"), Keys("application", "value")} { wantNode, wantFound := documentAtPath(applicationRoot, path) gotNode, gotFound := documentAtPathWithLookup(applicationRoot, path, lookup) if gotNode != wantNode || gotFound != wantFound { t.Errorf("indexed application lookup %s = (%p, %v), want (%p, %v)", path.String(), gotNode, gotFound, wantNode, wantFound) } } } func TestApplyResolutionMetadataPreservesOverlappingDeltaOrder(t *testing.T) { oldOrigin := Origin{Layer: LayerBase} parentOrigin := Origin{Layer: LayerResolution, Operation: ChangeNormalization} childOrigin := Origin{Layer: LayerResolution, Operation: ChangeDerivedDefault} leaf := &document.Node{Kind: document.String, Text: "value"} final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{ Key: "group", Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{ Key: "item", Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: leaf}}}, }}}, }}} metadata := &Metadata{ fields: map[string]FieldInfo{"/group/item/leaf": {Origin: oldOrigin}}, history: make(map[string][]Change), } resolved := resolutionResult{ final: final, deltas: []documentDelta{{path: Keys("group")}, {path: Keys("group", "item")}}, snapshots: []resolutionSnapshot{ {change: Change{Path: Keys("group"), Origin: parentOrigin}}, {change: Change{Path: Keys("group", "item"), Origin: childOrigin}}, }, } applyResolutionMetadata(metadata, resolved) field := metadata.fields["/group/item/leaf"] if field.Origin.Operation != childOrigin.Operation { t.Fatalf("final leaf origin operation = %q, want %q", field.Origin.Operation, childOrigin.Operation) } changes := metadata.history["/group/item/leaf"] if len(changes) != 2 { t.Fatalf("leaf history has %d entries, want 2: %#v", len(changes), changes) } if changes[0].Origin.Operation != parentOrigin.Operation || changes[1].Origin.Operation != childOrigin.Operation { t.Fatalf("leaf history operations = %q, %q; want %q, %q", changes[0].Origin.Operation, changes[1].Origin.Operation, parentOrigin.Operation, childOrigin.Operation) } for index, change := range changes { if len(change.Previous) != 1 || change.Previous[0].Layer != oldOrigin.Layer { t.Errorf("leaf history[%d] Previous = %#v, want original origin", index, change.Previous) } } }