package engine import ( "reflect" "strings" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestComposeDocumentsJoinsDisjointMappingsAndTracksClaims(t *testing.T) { base := parseCompositionDocument(t, "source-1", `services: api: enabled: true empty: {} `) fragment := parseCompositionDocument(t, "source-2", `services: worker: enabled: false values: [one, two] `) baseBefore := document.Clone(base) fragmentBefore := document.Clone(fragment) composed, diagnostics := composeDocuments(base, fragment) if len(diagnostics) != 0 { t.Fatalf("composeDocuments() diagnostics = %#v", diagnostics) } for _, path := range []Path{ Keys("services", "api", "enabled"), Keys("services", "worker", "enabled"), Keys("empty"), Keys("values"), } { if _, ok := documentAtPath(composed.root, path); !ok { t.Errorf("composed document is missing %s", path.String()) } } serviceOrigin := composed.origins[Keys("services").String()] wantLocations := []Location{ {SourceID: "source-1", Line: 2, Column: 3}, {SourceID: "source-2", Line: 2, Column: 3}, } if !reflect.DeepEqual(serviceOrigin, Origin{Layer: LayerBase, Locations: wantLocations}) { t.Fatalf("services origin = %#v, want composite origin", serviceOrigin) } wantClaimPaths := []string{"", "/services", "/services/api", "/services/api/enabled", "/empty", "", "/services", "/services/worker", "/services/worker/enabled", "/values"} gotClaimPaths := make([]string, len(composed.claims)) for index, claim := range composed.claims { gotClaimPaths[index] = claim.path.String() } if !reflect.DeepEqual(gotClaimPaths, wantClaimPaths) { t.Fatalf("claim paths = %#v, want %#v", gotClaimPaths, wantClaimPaths) } reversed, reversedDiagnostics := composeDocuments(fragment, base) if len(reversedDiagnostics) != 0 { t.Fatalf("reversed composeDocuments() diagnostics = %#v", reversedDiagnostics) } if deltas := compareConfigurationValues(composed.root, reversed.root); len(deltas) != 0 { t.Fatalf("input order changed effective values: %#v", deltas) } if !reflect.DeepEqual(base, baseBefore) || !reflect.DeepEqual(fragment, fragmentBefore) { t.Fatal("composition mutated an input document") } composed.root.Mapping[0].Value.Mapping[0].Value.Mapping[0].Value.Text = "changed" if !reflect.DeepEqual(base, baseBefore) || !reflect.DeepEqual(fragment, fragmentBefore) { t.Fatal("composed document aliases an input document") } } func TestComposeDocumentsRejectsEveryPresentNonMappingOverlap(t *testing.T) { tests := []struct { name string left string right string path string }{ {name: "equal scalar", left: "value: true\n", right: "value: true\n", path: "/value"}, {name: "different scalar", left: "value: true\n", right: "value: false\n", path: "/value"}, {name: "null", left: "value: null\n", right: "value: null\n", path: "/value"}, {name: "sequence", left: "value: [one]\n", right: "value: [two]\n", path: "/value"}, {name: "mapping and scalar", left: "value:\n nested: true\n", right: "value: text\n", path: "/value"}, {name: "scalar and mapping", left: "value: text\n", right: "value:\n nested: true\n", path: "/value"}, {name: "nested keyed entry", left: "items:\n shared:\n left: 1\n", right: "items:\n shared:\n left: 2\n", path: "/items/shared/left"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { left := parseCompositionDocument(t, "source-1", test.left) right := parseCompositionDocument(t, "source-2", test.right) _, diagnostics := composeDocuments(left, right) assertCompositionConflict(t, diagnostics, test.path, []string{"source-1", "source-2"}) }) } } func TestComposeDocumentsReportsAllClaimantsAtExactLocations(t *testing.T) { first := parseCompositionDocument(t, "source-1", "group:\n value: 1\n") second := parseCompositionDocument(t, "source-2", "group:\n value: 2\n") third := parseCompositionDocument(t, "source-3", "group:\n value: 3\n") _, diagnostics := composeDocuments(first, second, third) assertCompositionConflict(t, diagnostics, "/group/value", []string{"source-1", "source-2", "source-3"}) locations := append([]Location{*diagnostics[0].Primary}, diagnostics[0].Related...) for _, location := range locations { if location.Line != 2 || location.Column != 10 { t.Fatalf("claim location = %#v, want line 2 column 10", location) } } _, diagnostics = composeDocuments( parseCompositionDocument(t, "source-1", "group:\n left: 1\n"), parseCompositionDocument(t, "source-2", "group:\n right: 2\n"), parseCompositionDocument(t, "source-3", "group: blocked\n"), ) assertCompositionConflict(t, diagnostics, "/group", []string{"source-1", "source-2", "source-3"}) } func TestComposeDocumentsKeepsFirstConflictAndAppendedEntryOrder(t *testing.T) { first := parseCompositionDocument(t, "source-1", "catalog:\n first: 1\n shared: one\n later: one\n") second := parseCompositionDocument(t, "source-2", "catalog:\n second: 2\n shared: two\n later: two\n") third := parseCompositionDocument(t, "source-3", "catalog:\n third: 3\n shared: three\n") _, diagnostics := composeDocuments(first, second, third) assertCompositionConflict(t, diagnostics, "/catalog/shared", []string{"source-1", "source-2", "source-3"}) composed, diagnostics := composeDocuments( parseCompositionDocument(t, "source-1", "catalog:\n first: 1\n"), parseCompositionDocument(t, "source-2", "catalog:\n second: 2\n"), parseCompositionDocument(t, "source-3", "catalog:\n third: 3\n"), ) if len(diagnostics) != 0 { t.Fatalf("composeDocuments() diagnostics = %#v", diagnostics) } entries := composed.root.Mapping[0].Value.Mapping got := make([]string, len(entries)) for index, entry := range entries { got[index] = entry.Key } if want := []string{"first", "second", "third"}; !reflect.DeepEqual(got, want) { t.Fatalf("catalog keys = %#v, want %#v", got, want) } } func TestComposeDocumentsTreatsEmptyMappingsAsChildlessContributions(t *testing.T) { empty, diagnostics := composeDocuments( parseCompositionDocument(t, "source-1", "{}\n"), parseCompositionDocument(t, "source-2", "{}\n"), ) if len(diagnostics) != 0 || len(empty.root.Mapping) != 0 || len(empty.origins[""].Locations) != 2 { t.Fatalf("empty root composition = (%#v, %#v)", empty, diagnostics) } tests := []struct { name string left string right string }{ {name: "two empty mappings", left: "value: {}\n", right: "value: {}\n"}, {name: "empty then populated", left: "value: {}\n", right: "value: {nested: true}\n"}, {name: "populated then empty", left: "value: {nested: true}\n", right: "value: {}\n"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { composed, diagnostics := composeDocuments( parseCompositionDocument(t, "source-1", test.left), parseCompositionDocument(t, "source-2", test.right), ) if len(diagnostics) != 0 { t.Fatalf("composeDocuments() diagnostics = %#v", diagnostics) } if origin := composed.origins["/value"]; len(origin.Locations) != 2 { t.Fatalf("value origin = %#v, want both mapping locations", origin) } if test.name != "two empty mappings" { if _, ok := documentAtPath(composed.root, Keys("value", "nested")); !ok { t.Fatal("populated mapping contribution was not retained") } } }) } } func TestComposeDocumentsRejectsInvalidBoundaries(t *testing.T) { if composed, diagnostics := composeDocuments(); composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeComposeInvalid { t.Fatalf("empty composition = (%#v, %#v)", composed, diagnostics) } nonMapping := &document.Node{Kind: document.String, Location: document.Location{SourceID: "source-1", Line: 4, Column: 2}} if composed, diagnostics := composeDocuments(nonMapping); composed != nil || len(diagnostics) != 1 || diagnostics[0].Primary == nil || diagnostics[0].Primary.Line != 4 { t.Fatalf("non-mapping composition = (%#v, %#v)", composed, diagnostics) } } func TestComposeDocumentsAppendsRegisteredSequencesAndReturnsRanges(t *testing.T) { first := parseCompositionDocument(t, "source-1", "records: [{id: '', left: 1}, {id: ' spaced ', right: 1}]\nother: [one]\n") second := parseCompositionDocument(t, "source-2", "records: [{id: ' ', text: second}]\n") firstBefore, secondBefore := document.Clone(first), document.Clone(second) policy := sequencePolicy{identityFields: map[string]string{"/records": "id"}} composed, diagnostics := composeDocumentsWithPolicy(policy, first, second) if len(diagnostics) != 0 { t.Fatalf("composeDocumentsWithPolicy() diagnostics = %#v", diagnostics) } records, ok := documentAtPath(composed.root, Keys("records")) if !ok || records.Kind != document.Sequence || len(records.Sequence) != 3 { t.Fatalf("records = %#v, want three appended records", records) } if got := composed.sequenceRanges; !reflect.DeepEqual(got, []sequenceRange{ {sourceIndex: 0, path: Keys("records"), start: 0, end: 2}, {sourceIndex: 1, path: Keys("records"), start: 2, end: 3}, }) { t.Fatalf("sequence ranges = %#v", got) } if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) { t.Fatal("registered composition mutated an input") } composed.root.Mapping[0].Value.Sequence[0].Mapping[1].Value.Text = "changed" if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) { t.Fatal("composed sequence aliases an input") } } func TestComposeDocumentsProjectsSequenceOriginsToAssignedIndexes(t *testing.T) { first := parseCompositionDocument(t, "source-1", `mounted: records: - id: first name: first-name escaped/~: "7": - id: escaped-first name: escaped-first-name `) second := parseCompositionDocument(t, "source-2", `mounted: records: - id: second name: second-name escaped/~: "7": - id: escaped-second name: escaped-second-name `) firstBefore, secondBefore := document.Clone(first), document.Clone(second) policy := sequencePolicy{identityFields: map[string]string{ Keys("mounted", "records").String(): "id", Keys("escaped/~", "7").String(): "id", }} composed, diagnostics := composeDocumentsWithPolicy(policy, first, second) if len(diagnostics) != 0 { t.Fatalf("composeDocumentsWithPolicy() diagnostics = %#v", diagnostics) } for _, test := range []struct { path Path want Location }{ {path: Keys("mounted", "records").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 4, Column: 13}}, {path: Keys("escaped/~", "7").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 8, Column: 13}}, } { path, want := test.path, test.want origin, ok := composed.origins[path.String()] if !ok || !reflect.DeepEqual(origin.Locations, []Location{want}) { t.Errorf("origin at %s = %#v, want source-specific location %#v", path, origin, want) } } for _, test := range []struct { path Path wantSources []string }{ {path: Keys("mounted", "records"), wantSources: []string{"source-1", "source-2"}}, {path: Keys("escaped/~", "7"), wantSources: []string{"source-1", "source-2"}}, } { path, wantSources := test.path, test.wantSources origin := composed.origins[path.String()] gotSources := make([]string, len(origin.Locations)) for index, location := range origin.Locations { gotSources[index] = location.SourceID } if !reflect.DeepEqual(gotSources, wantSources) { t.Errorf("container origin at %s = %#v, want contributors %#v", path, gotSources, wantSources) } } if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) { t.Fatal("composition projection mutated an input") } copy := cloneComposedDocument(composed) copy.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID = "changed" for index := range copy.claims { if copy.claims[index].path.String() == "/mounted/records" { copy.claims[index].path.segments[0].key = "changed" break } } copy.sequenceRanges[0].path.segments[0].key = "changed" if got := composed.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID; got != "source-2" { t.Fatalf("copied origin mutation changed source evidence: %q", got) } foundClaim := false for _, claim := range composed.claims { if claim.path.String() == "/mounted/records" { foundClaim = true break } } if !foundClaim { t.Fatal("copied claim mutation changed source path") } if got := composed.sequenceRanges[0].path.String(); got != "/mounted/records" { t.Fatalf("copied range mutation changed assigned path: %q", got) } } func TestComposeDocumentsValidatesRegisteredSequenceSingletons(t *testing.T) { tests := []struct { name string body string path string wantCode Code }{ {name: "nonmapping item", body: "records: [plain]\n", path: "/records/0", wantCode: CodeComposeInvalid}, {name: "missing identity", body: "records: [{name: plain}]\n", path: "/records/0", wantCode: CodeComposeInvalid}, {name: "null identity", body: "records: [{id: null}]\n", path: "/records/0/id", wantCode: CodeComposeInvalid}, {name: "numeric identity", body: "records: [{id: 12}]\n", path: "/records/0/id", wantCode: CodeComposeInvalid}, {name: "boolean identity", body: "records: [{id: false}]\n", path: "/records/0/id", wantCode: CodeComposeInvalid}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, diagnostics := composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/records": "id"}}, parseCompositionDocument(t, "source-1", test.body), ) if len(diagnostics) != 1 || diagnostics[0].Code != test.wantCode || diagnostics[0].Path.String() != test.path { t.Fatalf("diagnostics = %#v, want %s at %s", diagnostics, test.wantCode, test.path) } if diagnostics[0].Primary == nil { t.Fatal("malformed sequence diagnostic has no source location") } }) } } func TestComposeDocumentsReportsAssignedPathForMalformedRegisteredSequenceContribution(t *testing.T) { _, diagnostics := composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/records": "id"}}, parseCompositionDocument(t, "source-1", "records: [{id: first}]\n"), parseCompositionDocument(t, "source-2", "records: [{name: malformed}]\n"), ) if len(diagnostics) != 1 || diagnostics[0].Code != CodeComposeInvalid || diagnostics[0].Path.String() != "/records/1" { t.Fatalf("diagnostics = %#v, want invalid item at /records/1", diagnostics) } if diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" { t.Fatalf("malformed item location = %#v, want source-2", diagnostics[0].Primary) } } func TestComposeDocumentsRejectsRegisteredDuplicateIdentities(t *testing.T) { tests := []struct { name string first string second string within bool }{ {name: "within identical bodies", first: "records: [{id: '', value: same}, {id: '', value: same}]\n", within: true}, {name: "within disjoint bodies", first: "records: [{id: same, left: 1}, {id: same, right: 2}]\n", within: true}, {name: "across identical bodies", first: "records: [{id: same, value: same}]\n", second: "records: [{id: same, value: same}]\n"}, {name: "across disjoint bodies", first: "records: [{id: same, left: 1}]\n", second: "records: [{id: same, right: 2}]\n"}, {name: "empty identity across lists", first: "records: [{id: ''}]\n", second: "records: [{id: ''}]\n"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { inputs := []*document.Node{parseCompositionDocument(t, "source-1", test.first)} if !test.within { inputs = append(inputs, parseCompositionDocument(t, "source-2", test.second)) } _, diagnostics := composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/records": "id"}}, inputs..., ) if len(diagnostics) != 1 || diagnostics[0].Code != CodeComposeConflict || diagnostics[0].Path.String() != "/records" { t.Fatalf("diagnostics = %#v, want duplicate conflict at /records", diagnostics) } if diagnostics[0].Primary == nil || len(diagnostics[0].Related) != 1 { t.Fatalf("duplicate locations = %#v", diagnostics[0]) } for _, secret := range []string{"same", "left", "right", "value"} { if strings.Contains(diagnostics[0].Message, secret) { t.Fatalf("diagnostic leaks %q: %q", secret, diagnostics[0].Message) } } }) } } func TestComposeDocumentsSkipsMissingAndNonSequenceRegistrations(t *testing.T) { policy := sequencePolicy{identityFields: map[string]string{ "/absent": "id", "/not-a-sequence": "id", "/catalog": "id", "/catalog/records": "id", }} composed, diagnostics := composeDocumentsWithPolicy(policy, parseCompositionDocument(t, "source-1", ` not-a-sequence: null catalog: records: [{id: present}] `)) if len(diagnostics) != 0 { t.Fatalf("composeDocumentsWithPolicy() diagnostics = %#v", diagnostics) } if len(composed.sequenceRanges) != 1 || composed.sequenceRanges[0].path.String() != "/catalog/records" { t.Fatalf("sequence ranges = %#v, want only the reached descendant sequence", composed.sequenceRanges) } composed, diagnostics = composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/records": "id"}}, parseCompositionDocument(t, "source-1", "records: []\n"), parseCompositionDocument(t, "source-2", "records: []\n"), ) if len(diagnostics) != 0 || len(composed.root.Mapping[0].Value.Sequence) != 0 { t.Fatalf("empty registered sequences = (%#v, %#v)", composed, diagnostics) } if got := composed.sequenceRanges; !reflect.DeepEqual(got, []sequenceRange{ {sourceIndex: 0, path: Keys("records"), start: 0, end: 0}, {sourceIndex: 1, path: Keys("records"), start: 0, end: 0}, }) { t.Fatalf("empty sequence ranges = %#v", got) } } func TestComposeDocumentsKeepsUnregisteredSequenceConflicts(t *testing.T) { _, diagnostics := composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/registered": "id"}}, parseCompositionDocument(t, "source-1", "unregistered: [one]\n"), parseCompositionDocument(t, "source-2", "unregistered: [two]\n"), ) assertCompositionConflict(t, diagnostics, "/unregistered", []string{"source-1", "source-2"}) } func TestComposeDocumentsKeepsNonSequenceRegisteredOverlapsConflicting(t *testing.T) { tests := []struct { name string first string second string }{ {name: "mapping and sequence", first: "records: {id: one}\n", second: "records: [{id: two}]\n"}, {name: "null and sequence", first: "records: null\n", second: "records: [{id: two}]\n"}, {name: "scalar and sequence", first: "records: value\n", second: "records: [{id: two}]\n"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, diagnostics := composeDocumentsWithPolicy( sequencePolicy{identityFields: map[string]string{"/records": "id"}}, parseCompositionDocument(t, "source-1", test.first), parseCompositionDocument(t, "source-2", test.second), ) assertCompositionConflict(t, diagnostics, "/records", []string{"source-1", "source-2"}) }) } } func parseCompositionDocument(t *testing.T, sourceID, input string) *document.Node { t.Helper() parsed, diagnostics := parseSource(Bytes(sourceID, []byte(input)), sourceID, defaultParseLimits()) if len(diagnostics) != 0 { t.Fatalf("parse diagnostics = %#v", diagnostics) } return parsed.document } func assertCompositionConflict(t *testing.T, diagnostics []Diagnostic, path string, sources []string) { t.Helper() if len(diagnostics) != 1 { t.Fatalf("diagnostics = %#v, want one conflict", diagnostics) } diagnostic := diagnostics[0] if diagnostic.Code != CodeComposeConflict || diagnostic.Path.String() != path || diagnostic.Primary == nil { t.Fatalf("diagnostic = %#v, want compose conflict at %s", diagnostic, path) } locations := append([]Location{*diagnostic.Primary}, diagnostic.Related...) gotSources := make([]string, len(locations)) for index, location := range locations { gotSources[index] = location.SourceID } if !reflect.DeepEqual(gotSources, sources) { t.Fatalf("claim sources = %#v, want %#v", gotSources, sources) } }