195 lines
7.7 KiB
Go
195 lines
7.7 KiB
Go
package configkit
|
|
|
|
import (
|
|
"reflect"
|
|
"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 := compareDocumentTrees(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 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 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)
|
|
}
|
|
}
|