Files
ConfigKit/internal/engine/history_sequence_test.go

476 lines
19 KiB
Go

package engine
import (
"bytes"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestSequenceHistoryReplaysAppendAtAssignedIndexes(t *testing.T) {
policy := sequencePolicy{identityFields: map[string]string{"/records": "id", "/other": "id"}}
first := parseCompositionDocument(t, "source-1", "records: [{id: a, value: first}]\nother: [{id: x}]\n")
second := parseCompositionDocument(t, "source-2", "records: [{id: b, value: second}]\nother: [{id: y}]\n")
composed, diagnostics := composeDocumentsWithPolicy(policy, first, second)
if len(diagnostics) != 0 {
t.Fatalf("compose diagnostics = %#v", diagnostics)
}
for applicationIndex, application := range composed.applications {
for path, want := range map[string]int{"/records": 1, "/other": 1} {
sequence, present := documentAtPath(application.value, mustParsePath(t, path))
if !present || sequence.Kind != document.Sequence || len(sequence.Sequence) != want {
t.Errorf("application %d retained %s = %#v, %v; want one source fragment item", applicationIndex, path, sequence, present)
}
}
}
working := parseCompositionDocument(t, "defaults", "records: [{id: default}]\nother: [{id: default}]\n")
index := new(mappingIndex)
for _, application := range composed.applications {
applyHistoryApplication(&working, application, index)
}
for path, want := range map[string][]string{
"/records": {"a", "b"},
"/other": {"x", "y"},
} {
sequence, present := documentAtPath(working, mustParsePath(t, path))
if !present || sequence.Kind != document.Sequence || len(sequence.Sequence) != len(want) {
t.Fatalf("replayed %s = %#v", path, sequence)
}
for itemIndex, id := range want {
field := sequence.Sequence[itemIndex].Mapping[0].Value.Text
if field != id {
t.Errorf("replayed %s[%d].id = %q, want %q", path, itemIndex, field, id)
}
}
}
application := composed.applications[1]
cloned := cloneHistoryApplications(composed.applications)
cloned[1].sequences[0].path.segments[0].key = "mutated"
if composed.applications[1].sequences[0].path.String() != "/records" {
t.Fatal("cloned sequence history path aliases its source")
}
addedPath := Keys("records").Index(1).Key("value")
if operation, relevant := applicationOperationPath(application, addedPath, nil); !relevant || operation.String() != addedPath.String() {
t.Fatalf("appended value operation = %s, %v", operation.String(), relevant)
}
if _, relevant := applicationOperationPath(application, Keys("records").Index(0).Key("value"), nil); relevant {
t.Fatal("later contribution was reported as changing an earlier record")
}
origin := historyApplicationOrigin(application, addedPath, nil)
if len(origin.Locations) != 1 || origin.Locations[0].SourceID != "source-2" {
t.Fatalf("appended value origin = %#v", origin)
}
overlay := historyApplication{
layer: LayerOverlay,
value: parseCompositionDocument(t, "overlay", "records: []\n"),
}
applyHistoryApplication(&working, overlay, index)
replaced, present := documentAtPath(working, Keys("records"))
if !present || replaced.Kind != document.Sequence || len(replaced.Sequence) != 0 {
t.Fatalf("ordinary later list replacement = %#v, %v", replaced, present)
}
}
func TestSequenceHistoryPreservesTierReplacementAndEmptyLists(t *testing.T) {
policy := sequencePolicy{identityFields: map[string]string{"/records": "id", "/independent": "id"}}
first, diagnostics := composeDocumentsWithPolicy(policy,
parseCompositionDocument(t, "underlay-1a", "records: [{id: shared}]\nindependent: [{id: x}]\n"),
parseCompositionDocument(t, "underlay-1b", "records: [{id: lower}]\n"),
)
if len(diagnostics) != 0 {
t.Fatalf("first tier diagnostics = %#v", diagnostics)
}
for index := range first.applications {
first.applications[index].layer = LayerUnderlay
}
second, diagnostics := composeDocumentsWithPolicy(policy,
parseCompositionDocument(t, "underlay-2", "records: [{id: shared}]\nindependent: []\n"),
)
if len(diagnostics) != 0 {
t.Fatalf("second tier diagnostics = %#v", diagnostics)
}
main, diagnostics := composeDocumentsWithPolicy(policy,
parseCompositionDocument(t, "main", "records: []\n"),
)
if len(diagnostics) != 0 {
t.Fatalf("main diagnostics = %#v", diagnostics)
}
assembly := newAssemblyAccumulator(first)
if diagnostics := assembly.applyOverlay(second.root, second.origins, second.applications, LayerUnderlay); len(diagnostics) != 0 {
t.Fatalf("second tier handoff diagnostics = %#v", diagnostics)
}
if diagnostics := assembly.applyOverlay(main.root, main.origins, main.applications, LayerBase); len(diagnostics) != 0 {
t.Fatalf("main handoff diagnostics = %#v", diagnostics)
}
if len(assembly.current.applications) != 4 {
t.Fatalf("retained source applications = %d, want 4", len(assembly.current.applications))
}
working := parseCompositionDocument(t, "defaults", "records: [{id: default}]\nindependent: [{id: default}]\n")
index := new(mappingIndex)
for _, application := range assembly.current.applications {
applyHistoryApplication(&working, application, index)
}
for path, want := range map[string][]string{
"/records": nil,
"/independent": nil,
} {
sequence, present := documentAtPath(working, mustParsePath(t, path))
if !present || sequence.Kind != document.Sequence || len(sequence.Sequence) != len(want) {
t.Errorf("replayed replacement %s = %#v, want an explicit empty sequence", path, sequence)
}
}
// The same identity may appear in separate tiers, while each tier still
// retains its individual source applications for explanation replay.
if got := []Layer{assembly.current.applications[0].layer, assembly.current.applications[1].layer,
assembly.current.applications[2].layer, assembly.current.applications[3].layer}; !reflect.DeepEqual(got, []Layer{LayerUnderlay, LayerUnderlay, LayerUnderlay, LayerBase}) {
t.Fatalf("application layers = %#v", got)
}
}
func TestHistoryMergeSupportsSemanticIndexedPrefixes(t *testing.T) {
root := parseCompositionDocument(t, "base", "records: [{id: one, helper: {value: old}}]\n")
application := historyApplication{
path: Keys("records").Index(0).Key("helper"),
value: parseCompositionDocument(t, "helper", "value: new\n"),
}
applyHistoryApplication(&root, application, new(mappingIndex))
value, present := documentAtPath(root, Keys("records").Index(0).Key("helper").Key("value"))
if !present || value.Text != "new" {
t.Fatalf("indexed helper replay = %#v, %v", value, present)
}
}
func TestProjectedSequenceHistoryAgreesAcrossMetadataAndExplanation(t *testing.T) {
policy := sequencePolicy{identityFields: map[string]string{"/records": "id"}}
composed, diagnostics := composeDocumentsWithPolicy(policy,
parseCompositionDocument(t, "first", "records: [{id: first, token: public}]"),
parseCompositionDocument(t, "second", "records: [{id: second, token: private}]"),
)
if len(diagnostics) != 0 {
t.Fatalf("compose diagnostics = %#v", diagnostics)
}
path := Keys("records").Index(1).Key("token")
var metadata Metadata
finalizeMetadataPaths(&metadata, composed.root, composed.applications)
if !metadata.hasHistoricalPath(path) {
t.Fatalf("projected metadata omitted shifted path %s: %#v", path, metadata.historicalPaths)
}
inspection := &Inspection{
metadata: metadata, baseline: &document.Node{Kind: document.Mapping},
input: composed.root, final: composed.root, applications: composed.applications,
}
inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool {
return reflect.DeepEqual(candidate, path)
}, metadata, composed.root, composed.applications)
explanation, ok := inspection.Explain(path)
if !ok || !explanation.Effective.Redacted {
t.Fatalf("shifted explanation = %#v, %v", explanation, ok)
}
var foundSecondOrigin bool
for _, change := range explanation.History {
for _, location := range change.Origin.Locations {
foundSecondOrigin = foundSecondOrigin || location.SourceID == "second"
}
}
if !foundSecondOrigin {
t.Fatalf("shifted history lost second source attribution: %#v", explanation.History)
}
var rendered bytes.Buffer
if err := inspection.WriteExplanation(&rendered, path); err != nil {
t.Fatal(err)
}
if !strings.Contains(rendered.String(), `path: "/records/1/token"`) || strings.Contains(rendered.String(), "private") {
t.Fatalf("shifted explanation rendering is incomplete or unsafe: %s", rendered.String())
}
}
func TestReplacedProjectedSensitiveRecordRemainsRedacted(t *testing.T) {
policy := sequencePolicy{identityFields: map[string]string{"/records": "id"}}
composed, diagnostics := composeDocumentsWithPolicy(policy,
parseCompositionDocument(t, "first", "records: [{id: first, token: public}]"),
parseCompositionDocument(t, "second", "records: [{id: second, token: PRIVATE-RECORD-CANARY}]"),
)
if len(diagnostics) != 0 {
t.Fatalf("compose diagnostics = %#v", diagnostics)
}
applications := append([]historyApplication(nil), composed.applications...)
applications = append(applications, historyApplication{
layer: LayerOverlay,
value: parseCompositionDocument(t, "replacement", "records: [{id: replacement, token: visible}]"),
})
final := applications[len(applications)-1].value
path := Keys("records").Index(1).Key("token")
var metadata Metadata
finalizeMetadataPaths(&metadata, final, applications)
inspection := &Inspection{
metadata: metadata, baseline: &document.Node{Kind: document.Mapping},
input: final, final: final, applications: applications,
}
inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool {
return reflect.DeepEqual(candidate, path)
}, metadata, final, applications)
explanation, ok := inspection.Explain(path)
if !ok || explanation.Effective.Present {
t.Fatalf("replaced shifted record explanation = %#v, %v", explanation, ok)
}
var rendered bytes.Buffer
if err := inspection.WriteExplanation(&rendered, path); err != nil {
t.Fatal(err)
}
if strings.Contains(rendered.String(), "PRIVATE-RECORD-CANARY") || !strings.Contains(rendered.String(), "[REDACTED]") {
t.Fatalf("replaced sensitive history leaked or was not redacted: %s", rendered.String())
}
}
func TestMappedUpsertProjectsSensitiveReplacementEvidence(t *testing.T) {
lowerRoot := parseCompositionDocument(t, "lower", `records:
- id: first
- id: middle
- id: last
token: MAPPED-SECRET-CANARY
`)
higherRoot := parseCompositionDocument(t, "higher", `records:
- id: last
token: visible
- id: first
- 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.Fatalf("combine diagnostics = %#v", diagnostics)
}
secretPath := Keys("records").Index(2).Key("token")
secretNode, present := documentAtPath(lowerRoot, Keys("records").Index(2).Key("token"))
if !present {
t.Fatal("sensitive source token is missing")
}
secretNode.Sensitive = true
application := historyApplication{
layer: LayerOverlay, value: higherRoot,
sequences: []sequenceHistoryContribution{{
path: Keys("records"), mode: sequenceHistoryMapped,
items: combined.higherItems, replacements: combined.replacements,
}},
}
application.sequencesByPath = indexSequenceHistoryContributions(application.sequences)
applications := []historyApplication{{value: lowerRoot}, application}
replayed := &document.Node{Kind: document.Mapping}
for _, current := range applications {
applyHistoryApplication(&replayed, current, new(mappingIndex))
}
replayedRecords, _ := documentAtPath(replayed, Keys("records"))
if !reflect.DeepEqual(replayedRecords.Sequence, combined.value.Sequence) {
t.Fatalf("replayed value = %#v, want combination = %#v", replayedRecords.Sequence, combined.value.Sequence)
}
finalRoot := document.Clone(lowerRoot)
finalRecords, _ := documentAtPath(finalRoot, Keys("records"))
finalRecords.Sequence = document.Clone(combined.value).Sequence
var metadata Metadata
finalizeMetadataPaths(&metadata, finalRoot, applications)
inspection := &Inspection{
metadata: metadata, baseline: &document.Node{Kind: document.Mapping},
input: finalRoot, final: finalRoot, applications: applications,
}
secretRule := func(candidate Path) bool { return reflect.DeepEqual(candidate, secretPath) }
inspection.secrets = classifySecretPaths(nil, secretRule, metadata, finalRoot, applications)
explanation, ok := inspection.Explain(secretPath)
if !ok || !explanation.Effective.Redacted {
t.Fatalf("mapped replacement explanation = %#v, %v", explanation, ok)
}
var rendered bytes.Buffer
if err := inspection.WriteExplanation(&rendered, secretPath); err != nil {
t.Fatal(err)
}
if strings.Contains(rendered.String(), "MAPPED-SECRET-CANARY") || !strings.Contains(rendered.String(), "[REDACTED]") {
t.Fatalf("mapped replacement history leaked or was not redacted: %s", rendered.String())
}
}
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)
if err != nil {
t.Fatal(err)
}
return path
}