Files
configkit/internal/engine/history_source_test.go

212 lines
9.2 KiB
Go

package engine
import (
"reflect"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestHistorySourceObservationsPreserveRawAndCompositeDefinitions(t *testing.T) {
t.Parallel()
baseline := parseDocumentForDecode(t, "defaults: {nullable: null}\n")
base := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n")
overlay := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n")
setHistoryDocumentSource(base, "base")
setHistoryDocumentSource(overlay, "overlay")
baseOrigins := make(map[string]Origin)
collectOverlayOrigins(baseOrigins, base, Path{}, LayerBase)
baseApplication := historyApplication{
layer: LayerBase, value: base,
origin: overlayOrigin(LayerBase, base.Location),
}
composition := &composedDocument{
root: document.Clone(base), origins: baseOrigins,
applications: []historyApplication{baseApplication},
}
overlayOrigins := make(map[string]Origin)
collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay)
assembly := newAssemblyAccumulator(composition)
overlayApplication := historyApplication{
layer: LayerOverlay, value: overlay,
origin: overlayOrigin(LayerOverlay, overlay.Location),
}
if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{overlayApplication}, LayerOverlay); len(diagnostics) != 0 {
t.Fatalf("apply overlay diagnostics = %#v", diagnostics)
}
builder := newHistoryBuilder()
builder.observeBaseline(baseline)
builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays)
model := builder.freeze()
defaultPath := Keys("defaults", "nullable")
baselineSelections := model.selectHistory(defaultPath)
if len(baselineSelections) != 1 || baselineSelections[0].phase != historyPhaseBaseline || baselineSelections[0].attribution.origin.Layer != LayerDefault || !baselineSelections[0].after {
t.Fatalf("baseline definition selections = %#v", baselineSelections)
}
if selections := model.selectHistory(Keys("defaults", "absent")); len(selections) != 0 {
t.Fatalf("absent baseline path selections = %#v", selections)
}
sameSelections := model.selectHistory(Keys("section", "same"))
var rawSources []string
var replacement bool
for _, selection := range sameSelections {
if selection.role == historyRoleContribution && selection.hasAttribution {
rawSources = append(rawSources, selection.attribution.origin.Locations[0].SourceID)
}
if selection.role == historyRoleOperation {
replacement = selection.before && selection.after
}
}
if !reflect.DeepEqual(rawSources, []string{"base", "overlay"}) || !replacement {
t.Fatalf("equal raw contributions and replacement = sources %#v, selections %#v", rawSources, sameSelections)
}
emptySelections := model.selectHistory(Keys("section", "empty"))
var composite *historyAttribution
for index := range emptySelections {
if emptySelections[index].role == historyRoleCompositeDefinition {
composite = &emptySelections[index].attribution
}
}
if composite == nil || !reflect.DeepEqual(locationSourceIDs(composite.origin.Locations), []string{"base", "overlay"}) {
t.Fatalf("empty mapping composite attribution = %#v; selections %#v", composite, emptySelections)
}
var emptySummary *historySourcePayload
for index := range model.sourcePayloads {
payload := &model.sourcePayloads[index]
if payload.kind == historySourceSummaryPayload && sameConcreteHistoryPath(payload.path, Keys("section", "empty")) {
emptySummary = payload
break
}
}
if emptySummary == nil || len(emptySummary.contributions) != 2 || emptySummary.contributions[0].id != 0 || emptySummary.contributions[1].id != 1 {
t.Fatalf("empty mapping summary contribution references = %#v", emptySummary)
}
}
func TestHistorySourceReplacementTracksConcreteBeforeAndAfterShapes(t *testing.T) {
t.Parallel()
base := parseDocumentForDecode(t, "shape: {'0': {leaf: old}}\n")
overlay := parseDocumentForDecode(t, "shape: [{leaf: new}]\n")
setHistoryDocumentSource(base, "base")
setHistoryDocumentSource(overlay, "overlay")
baseOrigins := make(map[string]Origin)
collectOverlayOrigins(baseOrigins, base, Path{}, LayerBase)
overlayOrigins := make(map[string]Origin)
collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay)
composition := &composedDocument{
root: document.Clone(base), origins: baseOrigins,
applications: []historyApplication{{layer: LayerBase, value: base, origin: overlayOrigin(LayerBase, base.Location)}},
}
assembly := newAssemblyAccumulator(composition)
if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{{layer: LayerOverlay, value: overlay, origin: overlayOrigin(LayerOverlay, overlay.Location)}}, LayerOverlay); len(diagnostics) != 0 {
t.Fatalf("apply overlay diagnostics = %#v", diagnostics)
}
if len(assembly.current.overlays) != 1 {
t.Fatalf("replacement records = %#v", assembly.current.overlays)
}
record := assembly.current.overlays[0]
if len(record.displaced) != 3 || len(record.introduced) != 3 {
t.Fatalf("displaced/introduction path counts = %d/%d", len(record.displaced), len(record.introduced))
}
if got := record.introduced[2].path; !sameConcreteHistoryPath(got, Keys("shape").Index(0).Key("leaf")) {
t.Fatalf("introduced descendant path = %#v", got)
}
builder := newHistoryBuilder()
builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays)
model := builder.freeze()
modelIntroduction := model.sourcePayloads[2].overlay.introduced[2]
cloned := cloneOverlayRecords(assembly.current.overlays)
record.introduced[2].path.segments[2].key = "mutated"
record.introduced[2].origin.Locations[0].SourceID = "mutated"
if got := cloned[0].introduced[2]; !sameConcreteHistoryPath(got.path, Keys("shape").Index(0).Key("leaf")) || got.origin.Locations[0].SourceID != "overlay" {
t.Fatalf("cloned introduced evidence changed with producer: %#v", got)
}
if !sameConcreteHistoryPath(modelIntroduction.path, Keys("shape").Index(0).Key("leaf")) || modelIntroduction.origin.Locations[0].SourceID != "overlay" {
t.Fatalf("model-owned introduced evidence changed with producer: %#v", modelIntroduction)
}
oldPath := Keys("shape", "0", "leaf")
newPath := Keys("shape").Index(0).Key("leaf")
oldSelections := model.selectHistory(oldPath)
newSelections := model.selectHistory(newPath)
if len(oldSelections) < 2 || oldSelections[len(oldSelections)-1].role != historyRoleOperation || !oldSelections[len(oldSelections)-1].before || oldSelections[len(oldSelections)-1].after {
t.Fatalf("removed key path selections = %#v", oldSelections)
}
if len(newSelections) < 2 || newSelections[len(newSelections)-1].role != historyRoleOperation || newSelections[len(newSelections)-1].before || !newSelections[len(newSelections)-1].after {
t.Fatalf("introduced index path selections = %#v", newSelections)
}
parsed, err := ParsePath("/shape/0/leaf")
if err != nil {
t.Fatal(err)
}
parsedSelections := model.selectHistory(parsed)
if len(parsedSelections) == 0 || !parsedSelections[len(parsedSelections)-1].before || !parsedSelections[len(parsedSelections)-1].after {
t.Fatalf("parsed path did not combine replacement sides: %#v", parsedSelections)
}
}
func TestOverlayReplacementCapturesIntroducedPathOrigins(t *testing.T) {
t.Parallel()
existing := parseDocumentForDecode(t, "old: value\n")
incoming := parseDocumentForDecode(t, "new: value\n")
setHistoryDocumentSource(existing, "base")
setHistoryDocumentSource(incoming, "overlay")
path := Keys("section")
origins := make(map[string]Origin)
collectOverlayOrigins(origins, existing, path, LayerBase)
incomingOrigins := make(map[string]Origin)
collectOverlayOrigins(incomingOrigins, incoming, path, LayerOverlay)
result := &composedDocument{
origins: origins,
}
recordOverlayReplacement(result, path, existing, incoming, LayerOverlay, true, incomingOrigins)
if len(result.overlays) != 1 || len(result.overlays[0].introduced) != 2 {
t.Fatalf("replacement introduced evidence = %#v", result.overlays)
}
introduced := result.overlays[0].introduced[1]
if !sameConcreteHistoryPath(introduced.path, path.Key("new")) || introduced.origin.Layer != LayerOverlay || introduced.origin.Locations[0].SourceID != "overlay" {
t.Fatalf("introduced replacement evidence = %#v", introduced)
}
}
func TestHistorySourceEvidenceLeavesEmptySourcePhaseUnpopulated(t *testing.T) {
t.Parallel()
builder := newHistoryBuilder()
builder.observeBaseline(parseDocumentForDecode(t, "defaults: {enabled: true}\n"))
builder.observeSourceEvidence(nil, nil, nil, nil)
model := builder.freeze()
if len(model.sourcePayloads) != 0 {
t.Fatalf("empty source input retained payloads: %#v", model.sourcePayloads)
}
for _, observation := range model.observations {
if observation.phase == historyPhaseSources {
t.Fatalf("empty source phase has an observation: %#v", observation)
}
}
}
func setHistoryDocumentSource(root *document.Node, sourceID string) {
walkDocument(root, Path{}, func(_ Path, node *document.Node) {
node.Location.SourceID = sourceID
})
}
func locationSourceIDs(locations []Location) []string {
ids := make([]string, len(locations))
for index, location := range locations {
ids[index] = location.SourceID
}
return ids
}