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 }