Extend history replay for mapped sequence items
This commit is contained in:
@@ -230,6 +230,170 @@ func TestReplacedProjectedSensitiveRecordRemainsRedacted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSequenceHistoryReplaysCombinationEvidence(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operation sequenceMode
|
||||
lower string
|
||||
higher string
|
||||
}{
|
||||
{
|
||||
name: "append",
|
||||
operation: sequenceModeAppend,
|
||||
lower: "records: [{id: first}, {id: last}]\n",
|
||||
higher: "records: [{id: added}]\n",
|
||||
},
|
||||
{
|
||||
name: "upsert",
|
||||
operation: sequenceModeUpsert,
|
||||
lower: `records:
|
||||
- id: first
|
||||
label: same
|
||||
nested: {removed: lower}
|
||||
- id: middle
|
||||
- id: last
|
||||
nested: {removed: lower}
|
||||
`,
|
||||
higher: `records:
|
||||
- id: last
|
||||
nested: {kept: higher}
|
||||
- id: first
|
||||
label: same
|
||||
- id: added
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "replace",
|
||||
operation: sequenceModeReplace,
|
||||
lower: "records: [{id: discarded}]\n",
|
||||
higher: "records: [{id: replacement}]\n",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
lowerRoot := parseCompositionDocument(t, "lower", test.lower)
|
||||
higherRoot := parseCompositionDocument(t, "higher", test.higher)
|
||||
lower, _ := documentAtPath(lowerRoot, Keys("records"))
|
||||
higher, _ := documentAtPath(higherRoot, Keys("records"))
|
||||
fields := []string(nil)
|
||||
if test.operation != sequenceModeReplace {
|
||||
fields = []string{"id"}
|
||||
}
|
||||
combined, diagnostics := combineSequence(lower, higher, sequenceCombinationPolicy(test.operation, fields...), sequenceCombinationContext{destination: Keys("records")})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("combine diagnostics = %#v", diagnostics)
|
||||
}
|
||||
|
||||
mode := sequenceHistoryMapped
|
||||
if test.operation == sequenceModeReplace {
|
||||
mode = sequenceHistoryReplace
|
||||
}
|
||||
length := 0
|
||||
if mode == sequenceHistoryReplace {
|
||||
length = len(combined.higherItems)
|
||||
}
|
||||
application := historyApplication{
|
||||
layer: LayerOverlay,
|
||||
value: higherRoot,
|
||||
sequences: []sequenceHistoryContribution{{
|
||||
path: Keys("records"), mode: mode, length: length,
|
||||
items: combined.higherItems, replacements: combined.replacements,
|
||||
}},
|
||||
}
|
||||
application.sequencesByPath = indexSequenceHistoryContributions(application.sequences)
|
||||
replayed := document.Clone(lowerRoot)
|
||||
applyHistoryApplication(&replayed, application, new(mappingIndex))
|
||||
got, present := documentAtPath(replayed, Keys("records"))
|
||||
if !present || !reflect.DeepEqual(got.Sequence, combined.value.Sequence) {
|
||||
t.Fatalf("replayed sequence = %#v, want combination = %#v", got, combined.value.Sequence)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappedSequenceHistoryReplacesWholeRecordsAndKeepsDisplacedEvidence(t *testing.T) {
|
||||
lowerRoot := parseCompositionDocument(t, "lower", `records:
|
||||
- id: first
|
||||
label: same
|
||||
- id: middle
|
||||
- id: last
|
||||
nested: {removed: LOWER-CANARY}
|
||||
`)
|
||||
higherRoot := parseCompositionDocument(t, "higher", `records:
|
||||
- id: last
|
||||
nested: {kept: higher}
|
||||
- id: first
|
||||
label: same
|
||||
- id: added
|
||||
`)
|
||||
lower, _ := documentAtPath(lowerRoot, Keys("records"))
|
||||
higher, _ := documentAtPath(higherRoot, Keys("records"))
|
||||
combined, diagnostics := combineSequence(
|
||||
lower, higher,
|
||||
sequenceCombinationPolicy(sequenceModeUpsert, "id"),
|
||||
sequenceCombinationContext{destination: Keys("records")},
|
||||
)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
application := historyApplication{
|
||||
layer: LayerOverlay, value: higherRoot,
|
||||
sequences: []sequenceHistoryContribution{{
|
||||
path: Keys("records"), mode: sequenceHistoryMapped,
|
||||
items: combined.higherItems, replacements: combined.replacements,
|
||||
}},
|
||||
}
|
||||
application.sequencesByPath = indexSequenceHistoryContributions(application.sequences)
|
||||
clonedApplications := cloneHistoryApplications([]historyApplication{application})
|
||||
clonedApplications[0].sequences[0].items[0].outputIndex = 99
|
||||
clonedApplications[0].sequences[0].replacements[0].outputIndex = 99
|
||||
if application.sequences[0].items[0].outputIndex == 99 || application.sequences[0].replacements[0].outputIndex == 99 {
|
||||
t.Fatal("cloned mapped history aliases its source projections")
|
||||
}
|
||||
|
||||
query := Keys("records").Index(2).Key("nested").Key("removed")
|
||||
operationPath, relevant := applicationOperationPath(application, query, nil)
|
||||
if !relevant || operationPath.String() != "/records/2" {
|
||||
t.Fatalf("removed descendant operation = %s, %v", operationPath.String(), relevant)
|
||||
}
|
||||
localPath, ok := historyApplicationLocalPath(application, Keys("records").Index(2).Key("nested").Key("kept"))
|
||||
if !ok || localPath.String() != "/records/0/nested/kept" {
|
||||
t.Fatalf("mapped effective path localizes to %s, %v", localPath.String(), ok)
|
||||
}
|
||||
origin := historyApplicationOrigin(application, operationPath, nil)
|
||||
if len(origin.Locations) != 1 || origin.Locations[0].SourceID != "higher" {
|
||||
t.Fatalf("replacement operation origin = %#v", origin)
|
||||
}
|
||||
var walkedMappedPath bool
|
||||
walkHistoryApplication(application, func(local, effective Path, _ *document.Node) {
|
||||
if local.String() == "/records/0/nested/kept" && effective.String() == "/records/2/nested/kept" {
|
||||
walkedMappedPath = true
|
||||
}
|
||||
})
|
||||
if !walkedMappedPath {
|
||||
t.Fatal("history walk did not project a noncontiguous incoming item")
|
||||
}
|
||||
|
||||
replayed := document.Clone(lowerRoot)
|
||||
applyHistoryApplication(&replayed, application, new(mappingIndex))
|
||||
sequence, _ := documentAtPath(replayed, Keys("records"))
|
||||
if _, present := documentAtPath(sequence.Sequence[2], Keys("nested", "removed")); present {
|
||||
t.Fatal("whole-record replacement retained an omitted descendant")
|
||||
}
|
||||
if value, present := documentAtPath(sequence.Sequence[2], Keys("nested", "kept")); !present || value.Text != "higher" {
|
||||
t.Fatalf("replacement descendant = %#v, %v", value, present)
|
||||
}
|
||||
if sequence.Sequence[1].Mapping[0].Value.Text != "middle" {
|
||||
t.Fatal("unchanged neighboring record moved or changed")
|
||||
}
|
||||
if value, present := documentAtPath(lowerRoot, Keys("records").Index(2).Key("nested").Key("removed")); !present || value.Text != "LOWER-CANARY" {
|
||||
t.Fatalf("displaced lower contribution was not retained as raw history: %#v, %v", value, present)
|
||||
}
|
||||
if got := application.sequences[0].replacements; !reflect.DeepEqual(got, combined.replacements) || len(got) != 2 {
|
||||
t.Fatalf("replacement evidence lost equal-valued or noncontiguous events: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func mustParsePath(t *testing.T, value string) Path {
|
||||
t.Helper()
|
||||
path, err := ParsePath(value)
|
||||
|
||||
Reference in New Issue
Block a user