233 lines
9.6 KiB
Go
233 lines
9.6 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)
|
|
}
|
|
|
|
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 mustParsePath(t *testing.T, value string) Path {
|
|
t.Helper()
|
|
path, err := ParsePath(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|