187 lines
8.1 KiB
Go
187 lines
8.1 KiB
Go
package engine
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"go.maximumdirect.net/configkit/internal/document"
|
|
)
|
|
|
|
func TestCLIOverrideRecordsTrackConcreteShapeChanges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
beforeText string
|
|
incomingText string
|
|
oldPath Path
|
|
introducedPath Path
|
|
}{
|
|
{
|
|
name: "mapping to sequence",
|
|
beforeText: "target: {'0': {old: true}}\n",
|
|
incomingText: "target: [{new: true}]\n",
|
|
oldPath: Keys("target", "0", "old"),
|
|
introducedPath: Keys("target").Index(0).Key("new"),
|
|
},
|
|
{
|
|
name: "sequence to mapping",
|
|
beforeText: "target: [{old: true}]\n",
|
|
incomingText: "target: {'0': {new: true}}\n",
|
|
oldPath: Keys("target").Index(0).Key("old"),
|
|
introducedPath: Keys("target", "0", "new"),
|
|
},
|
|
}
|
|
for _, test := range cases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
before := parseDocumentForDecode(t, test.beforeText)
|
|
incomingRoot := parseDocumentForDecode(t, test.incomingText)
|
|
setOverrideTestDocumentSource(incomingRoot, "cli-source")
|
|
incoming, _ := documentAtPath(incomingRoot, Keys("target"))
|
|
origins := make(map[string]Origin)
|
|
collectOverlayOrigins(origins, before, Path{}, LayerMain)
|
|
records := overrideMetadataRecords(decodedConfiguration{final: before, origins: origins}, []preparedOverride{{
|
|
path: Keys("target"), value: incoming, source: SourceInfo{ID: "cli-source"},
|
|
}})
|
|
if len(records) != 1 {
|
|
t.Fatalf("override records = %#v", records)
|
|
}
|
|
record := records[0]
|
|
if !containsConcreteDisplacement(record.displaced, test.oldPath) {
|
|
t.Fatalf("displaced shape %s missing from %#v", test.oldPath, record.displaced)
|
|
}
|
|
introduced := findHistoryPathOrigin(record.introduced, test.introducedPath)
|
|
if introduced == nil || introduced.origin.Layer != LayerCLI || len(introduced.origin.Locations) != 1 || introduced.origin.Locations[0].SourceID != "cli-source" {
|
|
t.Fatalf("introduced shape %s has no CLI attribution: %#v", test.introducedPath, record.introduced)
|
|
}
|
|
application := historyApplication{
|
|
layer: LayerCLI, path: Keys("target"), value: incoming,
|
|
origin: overlayOrigin(LayerCLI, document.Location{SourceID: "cli-source"}),
|
|
}
|
|
builder := newHistoryBuilder()
|
|
builder.setScratch(before)
|
|
builder.observeCLIEvidence([]historyApplication{application}, records)
|
|
model := builder.freeze()
|
|
oldSelections := model.selectHistory(test.oldPath)
|
|
if len(oldSelections) != 1 || oldSelections[0].role != historyRoleOperation || !oldSelections[0].before || oldSelections[0].after {
|
|
t.Fatalf("displaced CLI shape %s = %#v", test.oldPath, oldSelections)
|
|
}
|
|
introducedSelections := model.selectHistory(test.introducedPath)
|
|
introducedOperation := false
|
|
for _, selection := range introducedSelections {
|
|
introducedOperation = introducedOperation || selection.role == historyRoleOperation && !selection.before && selection.after
|
|
}
|
|
if !introducedOperation {
|
|
t.Fatalf("introduced CLI shape %s selections = %#v", test.introducedPath, introducedSelections)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCLIHistoryEvidenceKeepsCheckpointsAndConditionalReconciliation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
structural := parseDocumentForDecode(t, "count: 7\nsettings: {a: 1, b: 2}\nitems: {old: value}\n")
|
|
preCLI := parseDocumentForDecode(t, "count: '7'\nsettings: {a: 1, b: 2}\nitems: {old: value}\ndefaulted: 9\n")
|
|
preOrigins := map[string]Origin{
|
|
Keys("count").String(): {Layer: LayerMain, Locations: []Location{{SourceID: "input"}}},
|
|
Keys("defaulted").String(): {Layer: LayerDefault},
|
|
}
|
|
countRoot := parseDocumentForDecode(t, "count: 7\n")
|
|
settingsRoot := parseDocumentForDecode(t, "settings: {a: 1}\n")
|
|
itemsRoot := parseDocumentForDecode(t, "items: []\n")
|
|
setOverrideTestDocumentSource(countRoot, "cli-count")
|
|
setOverrideTestDocumentSource(settingsRoot, "cli-settings")
|
|
setOverrideTestDocumentSource(itemsRoot, "cli-items")
|
|
countValue, _ := documentAtPath(countRoot, Keys("count"))
|
|
settingsValue, _ := documentAtPath(settingsRoot, Keys("settings"))
|
|
itemsValue, _ := documentAtPath(itemsRoot, Keys("items"))
|
|
overrides := []preparedOverride{
|
|
{path: Keys("count"), value: countValue, source: SourceInfo{ID: "cli-count"}},
|
|
{path: Keys("settings"), value: settingsValue, source: SourceInfo{ID: "cli-settings"}},
|
|
{path: Keys("items"), value: itemsValue, source: SourceInfo{ID: "cli-items"}},
|
|
}
|
|
records := overrideMetadataRecords(decodedConfiguration{final: preCLI, origins: preOrigins}, overrides)
|
|
applications := make([]historyApplication, len(overrides))
|
|
for index, override := range overrides {
|
|
applications[index] = historyApplication{
|
|
layer: LayerCLI, path: override.path, value: override.value,
|
|
origin: overlayOrigin(LayerCLI, document.Location{SourceID: override.source.ID}),
|
|
}
|
|
}
|
|
postCLI := parseDocumentForDecode(t, "count: '7'\nsettings: {a: 1, b: 2}\nitems: []\ndefaulted: 9\n")
|
|
postOrigins := map[string]Origin{
|
|
Keys("count").String(): {Layer: LayerCLI, Locations: []Location{{SourceID: "cli-count"}}},
|
|
Keys("defaulted").String(): {Layer: LayerDefault},
|
|
}
|
|
|
|
builder := newHistoryBuilder()
|
|
builder.setScratch(structural)
|
|
builder.observePreCLICheckpoint(preCLI, preOrigins)
|
|
builder.observeCLIEvidence(applications, records)
|
|
builder.observePostCLICheckpoint(postCLI, postOrigins)
|
|
model := builder.freeze()
|
|
|
|
if model.preCLICheckpoint == nil || model.postCLICheckpoint == nil ||
|
|
len(model.cliPayloads) != 6 {
|
|
t.Fatalf("checkpoint/CLI payload ownership = pre:%#v post:%#v CLI:%d", model.preCLICheckpoint, model.postCLICheckpoint, len(model.cliPayloads))
|
|
}
|
|
preValue, _ := documentAtPath(model.preCLICheckpoint.root, Keys("count"))
|
|
postValue, _ := documentAtPath(model.postCLICheckpoint.root, Keys("count"))
|
|
if preValue.Kind != document.String || preValue.Text != "7" || postValue.Kind != document.String || postValue.Text != "7" {
|
|
t.Fatalf("typed checkpoint values = %#v / %#v", preValue, postValue)
|
|
}
|
|
|
|
count := model.selectHistory(Keys("count"))
|
|
var sawPreDecoding, sawEqualCLI, sawPostDecoding bool
|
|
for _, selection := range count {
|
|
switch selection.phase {
|
|
case historyPhasePreCLI:
|
|
sawPreDecoding = selection.role == historyRoleCheckpoint && selection.before && selection.after &&
|
|
selection.attribution.origin.Operation == ChangeDecoding && len(selection.attribution.reasons) != 0
|
|
case historyPhaseCLI:
|
|
sawEqualCLI = sawEqualCLI || selection.role == historyRoleOperation && selection.after
|
|
case historyPhasePostCLI:
|
|
sawPostDecoding = selection.role == historyRoleCheckpoint && selection.before && selection.after &&
|
|
selection.attribution.origin.Layer == LayerCLI && selection.attribution.origin.Operation == ChangeDecoding
|
|
}
|
|
}
|
|
if !sawPreDecoding || !sawEqualCLI || !sawPostDecoding {
|
|
t.Fatalf("checkpoint and equal CLI selections = %#v", count)
|
|
}
|
|
if metadataHistoryRole(historyRoleCheckpoint) || !metadataHistoryRole(historyRoleOperation) {
|
|
t.Fatal("typed reconciliation checkpoint leaked into metadata history roles")
|
|
}
|
|
|
|
defaulted := model.selectHistory(Keys("defaulted"))
|
|
if len(defaulted) != 1 || defaulted[0].phase != historyPhasePreCLI || defaulted[0].before || !defaulted[0].after ||
|
|
defaulted[0].attribution.origin.Layer != LayerDefault || len(defaulted[0].attribution.reasons) == 0 {
|
|
t.Fatalf("retained default checkpoint = %#v", defaulted)
|
|
}
|
|
removed := model.selectHistory(Keys("items", "old"))
|
|
if len(removed) != 1 || removed[0].phase != historyPhaseCLI || removed[0].role != historyRoleOperation || !removed[0].before || removed[0].after {
|
|
t.Fatalf("whole-sequence CLI removal = %#v", removed)
|
|
}
|
|
untouched := model.selectHistory(Keys("settings", "b"))
|
|
if len(untouched) != 0 {
|
|
t.Fatalf("partial mapping CLI override attributed an untouched key: %#v", untouched)
|
|
}
|
|
}
|
|
|
|
func containsConcreteDisplacement(displaced []displacedDefinition, path Path) bool {
|
|
for _, candidate := range displaced {
|
|
if sameConcreteHistoryPath(candidate.path, path) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findHistoryPathOrigin(introduced []historyPathOrigin, path Path) *historyPathOrigin {
|
|
for index := range introduced {
|
|
if sameConcreteHistoryPath(introduced[index].path, path) {
|
|
return &introduced[index]
|
|
}
|
|
}
|
|
return nil
|
|
}
|