283 lines
11 KiB
Go
283 lines
11 KiB
Go
package engine
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"go.maximumdirect.net/configkit/internal/document"
|
|
)
|
|
|
|
type historyBudgetSimpleConfig struct {
|
|
Value string
|
|
}
|
|
|
|
type historyBudgetRecord struct {
|
|
Value string
|
|
Credential Secret
|
|
}
|
|
|
|
type historyBudgetLayeredConfig struct {
|
|
Parent historyBudgetRecord
|
|
Child historyBudgetRecord
|
|
}
|
|
|
|
func retainedInspectionNodeCountForTest(inspection *Inspection) int {
|
|
seen := make(map[*document.Node]struct{})
|
|
model := inspection.history
|
|
stack := []*document.Node{model.baseline, model.final, inspection.final}
|
|
for _, payload := range model.sourcePayloads {
|
|
if payload.kind == historySourceApplicationPayload {
|
|
stack = append(stack, payload.application.value)
|
|
}
|
|
}
|
|
for _, payload := range model.inheritancePayloads {
|
|
for _, operation := range payload.operations {
|
|
stack = append(stack, operation.parent, operation.effective)
|
|
}
|
|
}
|
|
for _, payload := range model.cliPayloads {
|
|
if payload.kind == historySourceApplicationPayload {
|
|
stack = append(stack, payload.application.value)
|
|
}
|
|
}
|
|
if model.preCLICheckpoint != nil {
|
|
stack = append(stack, model.preCLICheckpoint.root)
|
|
}
|
|
if model.postCLICheckpoint != nil {
|
|
stack = append(stack, model.postCLICheckpoint.root)
|
|
}
|
|
if model.resolutionPayload != nil {
|
|
for _, snapshot := range model.resolutionPayload.snapshots {
|
|
stack = append(stack, snapshot.before, snapshot.after)
|
|
}
|
|
}
|
|
|
|
for len(stack) != 0 {
|
|
last := len(stack) - 1
|
|
node := stack[last]
|
|
stack = stack[:last]
|
|
if node == nil {
|
|
continue
|
|
}
|
|
if _, exists := seen[node]; exists {
|
|
continue
|
|
}
|
|
seen[node] = struct{}{}
|
|
for _, entry := range node.Mapping {
|
|
stack = append(stack, entry.Value)
|
|
}
|
|
stack = append(stack, node.Sequence...)
|
|
}
|
|
return len(seen)
|
|
}
|
|
|
|
func TestLoadHistoryBudgetUsesExactRetainedNodeCount(t *testing.T) {
|
|
load := func(historyLimit int, validations *int) (*Loaded[historyBudgetSimpleConfig], error) {
|
|
return loadWithSecretLookupLimit(
|
|
Bytes("main", []byte("value: retained\n")),
|
|
testSecretBinding(),
|
|
func(string) (string, bool) { return "", false },
|
|
historyLimit,
|
|
Options[historyBudgetSimpleConfig]{Definition: Definition[historyBudgetSimpleConfig]{
|
|
Validate: func(*historyBudgetSimpleConfig, *Validation) { *validations++ },
|
|
}},
|
|
)
|
|
}
|
|
|
|
var baselineValidations int
|
|
baseline, err := load(10000, &baselineValidations)
|
|
if err != nil {
|
|
t.Fatalf("baseline load: %v", err)
|
|
}
|
|
want := retainedInspectionNodeCountForTest(baseline.Inspection)
|
|
if want == 0 {
|
|
t.Fatal("independent retained-tree count was empty")
|
|
}
|
|
|
|
var exactValidations int
|
|
loaded, err := load(want, &exactValidations)
|
|
if err != nil || loaded == nil {
|
|
t.Fatalf("load at exact retained-node count %d: result=%v error=%v", want, loaded, err)
|
|
}
|
|
if got := retainedInspectionNodeCountForTest(loaded.Inspection); got != want {
|
|
t.Fatalf("retained node count = %d, want %d", got, want)
|
|
}
|
|
if exactValidations != 1 {
|
|
t.Fatalf("validation calls at exact count = %d, want 1", exactValidations)
|
|
}
|
|
|
|
var rejectedValidations int
|
|
loaded, err = load(want-1, &rejectedValidations)
|
|
if loaded != nil || !hasHistoryLimitDiagnostic(t, err) {
|
|
t.Fatalf("load below retained-node count: result=%v error=%v", loaded, err)
|
|
}
|
|
if rejectedValidations != 0 {
|
|
t.Fatalf("validation ran before history rejection: %d calls", rejectedValidations)
|
|
}
|
|
}
|
|
|
|
func TestHistoryNodeBudgetIncludesFinalInspectionProjection(t *testing.T) {
|
|
final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
|
Key: "value", Value: &document.Node{Kind: document.String, Text: "final"},
|
|
}}}
|
|
builder := newHistoryBuilder()
|
|
model := builder.freeze()
|
|
inspection := &Inspection{history: model, metadata: Metadata{historyModel: model}, final: final}
|
|
|
|
counter := newRetainedNodeCounter(2)
|
|
admitted := addInspectionHistory(counter, inspection)
|
|
if !admitted || counter.count != 2 {
|
|
t.Fatalf("final inspection projection was not counted exactly: admitted=%t count=%d", admitted, counter.count)
|
|
}
|
|
counter = newRetainedNodeCounter(1)
|
|
if addInspectionHistory(counter, inspection) {
|
|
t.Fatal("final inspection projection did not consume the node budget")
|
|
}
|
|
}
|
|
|
|
func TestLayeredHistoryBudgetRejectsBeforeSecretsAndValidation(t *testing.T) {
|
|
type callbackCounts struct {
|
|
lookups int
|
|
validations int
|
|
}
|
|
makeRequest := func(counts *callbackCounts) loadRequest[historyBudgetLayeredConfig] {
|
|
return loadRequest[historyBudgetLayeredConfig]{
|
|
main: loadInput{source: Bytes("main", []byte("child:\n extends: parent\n"))},
|
|
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("underlay", []byte(
|
|
"parent:\n value: inherited\n credential: HISTORY_BUDGET_SECRET\n",
|
|
))}}}},
|
|
overrides: []Override{{Path: Keys("child", "value"), Value: "cli", Flag: "--child-value"}},
|
|
definition: Definition[historyBudgetLayeredConfig]{
|
|
Resolve: func(configuration *historyBudgetLayeredConfig, resolution *Resolution) error {
|
|
configuration.Child.Value += "-resolved"
|
|
resolution.RecordNormalization(Keys("child", "value"), "normalize child")
|
|
resolution.RecordDerivedDefault(Keys("child"), "record enclosing child state")
|
|
return nil
|
|
},
|
|
Validate: func(*historyBudgetLayeredConfig, *Validation) { counts.validations++ },
|
|
},
|
|
}
|
|
}
|
|
lookup := func(counts *callbackCounts) secretLookup {
|
|
return func(name string) (string, bool) {
|
|
counts.lookups++
|
|
if name != "HISTORY_BUDGET_SECRET" {
|
|
t.Errorf("secret lookup name = %q", name)
|
|
}
|
|
return "resolved-secret", true
|
|
}
|
|
}
|
|
|
|
baselineCounts := &callbackCounts{}
|
|
baseline, err := loadGroupedWithSecretLookupLimit(makeRequest(baselineCounts), testSecretBinding(), lookup(baselineCounts), 10000)
|
|
if err != nil {
|
|
t.Fatalf("baseline grouped load: %v", err)
|
|
}
|
|
want := retainedInspectionNodeCountForTest(baseline.Inspection)
|
|
if want == 0 {
|
|
t.Fatal("independent retained-tree count was empty")
|
|
}
|
|
if baseline.Inspection.history == nil || baseline.Inspection.metadata.historyModel != baseline.Inspection.history || len(baseline.Inspection.history.inheritancePayloads) == 0 {
|
|
t.Fatal("layered load did not publish its shared inheritance evidence owner")
|
|
}
|
|
resolution := baseline.Inspection.history.resolutionPayload
|
|
if resolution == nil || len(resolution.snapshots) != 2 || len(resolution.replayGroups) == 0 {
|
|
t.Fatalf("layered load did not retain overlapping original resolution evidence: %#v", resolution)
|
|
}
|
|
resolutionPaths := make(map[string]bool, len(resolution.snapshots))
|
|
for _, snapshot := range resolution.snapshots {
|
|
resolutionPaths[snapshot.change.Path.String()] = true
|
|
}
|
|
if !resolutionPaths["/child"] || !resolutionPaths["/child/value"] {
|
|
t.Fatalf("layered load resolution snapshots have paths %v, want /child and /child/value", resolutionPaths)
|
|
}
|
|
group := resolution.replayGroups[0]
|
|
groupRecords := make(map[int]bool, len(group.originalRecordIDs))
|
|
for _, id := range group.originalRecordIDs {
|
|
groupRecords[id] = true
|
|
}
|
|
if len(groupRecords) != 2 || !groupRecords[0] || !groupRecords[1] || group.change.Path.String() != "/child" {
|
|
t.Fatalf("layered load did not group both overlapping original records: %#v", group)
|
|
}
|
|
exactCounts := &callbackCounts{}
|
|
loaded, err := loadGroupedWithSecretLookupLimit(makeRequest(exactCounts), testSecretBinding(), lookup(exactCounts), want)
|
|
if err != nil || loaded == nil {
|
|
t.Fatalf("grouped load at exact retained-node count %d: result=%v error=%v", want, loaded, err)
|
|
}
|
|
if got := retainedInspectionNodeCountForTest(loaded.Inspection); got != want {
|
|
t.Fatalf("grouped retained node count = %d, want %d", got, want)
|
|
}
|
|
if loaded.Config.Child.Value != "cli-resolved" || exactCounts.lookups != 1 || exactCounts.validations != 1 {
|
|
t.Fatalf("grouped lifecycle result=%#v callbacks=%#v", loaded.Config, exactCounts)
|
|
}
|
|
|
|
rejectedCounts := &callbackCounts{}
|
|
loaded, err = loadGroupedWithSecretLookupLimit(makeRequest(rejectedCounts), testSecretBinding(), lookup(rejectedCounts), want-1)
|
|
if loaded != nil || !hasHistoryLimitDiagnostic(t, err) {
|
|
t.Fatalf("grouped load below retained-node count: result=%v error=%v", loaded, err)
|
|
}
|
|
if rejectedCounts.lookups != 0 || rejectedCounts.validations != 0 {
|
|
t.Fatalf("callbacks ran before history rejection: %#v", rejectedCounts)
|
|
}
|
|
}
|
|
|
|
func TestPreparedInheritanceEvidenceSharesOwnerAndDetachesPublicViews(t *testing.T) {
|
|
input := parseCompositionDocument(t, "inheritance", "parent: {value: shared}\nchild: {extends: parent}\n")
|
|
expanded, diagnostics := expandInheritanceWithOrigins(input, nil, defaultProjectionLimits())
|
|
if len(diagnostics) != 0 {
|
|
t.Fatal(diagnostics)
|
|
}
|
|
baseline := &document.Node{Kind: document.Mapping}
|
|
builder := newHistoryBuilder()
|
|
builder.observeBaseline(baseline)
|
|
builder.setScratch(input)
|
|
builder.observeInheritanceEvidence(expanded.operations)
|
|
builder.observePreCLICheckpoint(expanded.root, expanded.origins)
|
|
builder.observePostCLICheckpoint(expanded.root, expanded.origins)
|
|
builder.prefixReady = true
|
|
metadata := Metadata{historyBuilder: builder}
|
|
resolved := resolvedLoad[historyBudgetSimpleConfig]{
|
|
metadata: metadata, final: expanded.root,
|
|
}
|
|
counter := newRetainedNodeCounter(100)
|
|
inspection, ok := prepareInspectionHistory(resolved, counter)
|
|
if !ok {
|
|
t.Fatal("preparation rejected the shared evidence owner")
|
|
}
|
|
if inspection.history == nil || inspection.metadata.historyModel != inspection.history || len(inspection.history.inheritancePayloads) != 1 {
|
|
t.Fatalf("history owners = inspection %p, metadata %p, inheritance payloads %d", inspection.history, inspection.metadata.historyModel, len(inspection.history.inheritancePayloads))
|
|
}
|
|
path := Keys("child", "value")
|
|
first := inspection.Metadata().History(path)
|
|
if len(first) == 0 {
|
|
t.Fatal("metadata projection omitted inherited history")
|
|
}
|
|
first[0].Origin.Locations[0].SourceID = "mutated metadata result"
|
|
first[0].Dependencies[0].segments[0].key = "mutated metadata dependency"
|
|
if explanation, found := inspection.Explain(path); !found || len(explanation.History) == 0 || explanation.History[0].Origin.Locations[0].SourceID == "mutated metadata result" {
|
|
t.Fatalf("metadata result mutation leaked into explanation: %#v, %v", explanation, found)
|
|
}
|
|
explanation, _ := inspection.Explain(path)
|
|
explanation.History[0].Origin.Locations[0].SourceID = "mutated explanation result"
|
|
second := inspection.Metadata().History(path)
|
|
if second[0].Origin.Locations[0].SourceID == "mutated explanation result" || second[0].Dependencies[0].String() == "/mutated metadata dependency" {
|
|
t.Fatalf("explanation mutation leaked into metadata: %#v", second)
|
|
}
|
|
}
|
|
|
|
func hasHistoryLimitDiagnostic(t *testing.T, err error) bool {
|
|
t.Helper()
|
|
if err == nil {
|
|
return false
|
|
}
|
|
loadErr, ok := err.(*LoadError)
|
|
if !ok {
|
|
t.Fatalf("error type = %T, want *LoadError", err)
|
|
}
|
|
for _, diagnostic := range loadErr.Diagnostics() {
|
|
if diagnostic.Code == CodeHistoryLimitExceeded {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|