Files
configkit/internal/engine/history_cli_test.go

192 lines
8.3 KiB
Go

package engine
import (
"testing"
"gitea.maximumdirect.net/eric/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{}, LayerBase)
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: LayerBase, 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)
preCount, _ := documentAtPath(preCLI, Keys("count"))
postCount, _ := documentAtPath(postCLI, Keys("count"))
preCount.Text = "producer mutation"
postCount.Text = "producer mutation"
model := builder.freeze()
if model.preCLICheckpoint == nil || model.postCLICheckpoint == nil ||
model.preCLICheckpoint.root == preCLI || model.postCLICheckpoint.root == postCLI ||
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
}