Files
ConfigKit/internal/engine/explain_test.go

523 lines
21 KiB
Go

package engine
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
type explanationConcurrency struct {
Transcription struct {
Concurrency int `yaml:"concurrency"`
} `yaml:"transcription"`
}
func TestExplainShowsDefaultBaseAndEffectiveOverlayValue(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "config.yml", "configkit:\n imports: [{path: transcription.yml}]\n")
writeImportFile(t, directory, "transcription.yml", "transcription:\n concurrency: 8\n")
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), "transcription:\n concurrency: 1\n")
result, err := testLoad[explanationConcurrency](File(mainPath), Options[explanationConcurrency]{
Imports: true, Overlay: "testing",
Definition: Definition[explanationConcurrency]{Defaults: func() explanationConcurrency {
value := explanationConcurrency{}
value.Transcription.Concurrency = 4
return value
}},
})
if err != nil {
t.Fatal(err)
}
explanation, ok := result.Explain(Keys("transcription", "concurrency"))
if !ok {
t.Fatal("known path was not explained")
}
if got := explanationValues(explanation); !reflect.DeepEqual(got, []string{"4\n", "8\n", "1\n"}) {
t.Fatalf("history values = %#v", got)
}
if explanation.Effective.YAML != "1\n" || explanation.Origin.Layer != LayerOverlay ||
len(explanation.Origin.Locations) != 1 || explanation.Origin.Locations[0].SourceID != "source-3" ||
explanation.Origin.Locations[0].Line != 2 {
t.Fatalf("effective explanation = %#v", explanation)
}
var rendered bytes.Buffer
if err := result.WriteExplanation(&rendered, Keys("transcription", "concurrency")); err != nil ||
!strings.Contains(rendered.String(), `layer="overlay"`) ||
!strings.Contains(rendered.String(), `label="overlays/testing.yml"`) {
t.Fatalf("rendered explanation = %v\n%s", err, rendered.String())
}
}
type explanationConfig struct {
Concurrency int `yaml:"concurrency"`
Equal string `yaml:"equal"`
Limit int `yaml:"limit"`
Items []string `yaml:"items"`
Labels map[string]string `yaml:"labels"`
Dynamic any `yaml:"dynamic"`
Maybe *string `yaml:"maybe"`
Empty []string `yaml:"empty"`
Credential Secret `yaml:"credential"`
Classified Secret `yaml:"classified"`
DerivedA string `yaml:"derived_a"`
DerivedB string `yaml:"derived_b"`
}
func TestExplainReplaysCLIResolutionContainersAndSafeHistoricalValues(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "config.yml", `concurrency: 8
equal: same
limit: wrong-before-overlay
items: [base, removed]
labels: {base: retained}
dynamic: {retired: HISTORICAL_EXPLAIN_CANARY, public: visible}
credential: APP_SECRET
classified: APP_SECRET
`)
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), `concurrency: 1
equal: same
limit: 7
items: [overlay]
labels: {overlay: retained}
dynamic: selected
`)
result, err := testLoadWithSecretLookup(File(mainPath), func(name string) (string, bool) {
if name != "APP_SECRET" {
t.Fatalf("secret lookup = %q", name)
}
return "RESOLVED_EXPLAIN_CANARY", true
}, Options[explanationConfig]{
Overlay: "testing",
Overrides: []Override{
{Path: Keys("concurrency"), Value: 2, Flag: "--concurrency"},
{Path: Keys("equal"), Value: "same", Flag: "--equal"},
{Path: Keys("items"), Value: []any{"cli", "restored"}, Flag: "--items"},
{Path: Keys("labels"), Value: map[string]any{"cli": "retained"}, Flag: "--label"},
},
Definition: Definition[explanationConfig]{
Defaults: func() explanationConfig {
return explanationConfig{
Concurrency: 4, Equal: "same", Labels: map[string]string{"default": "retained"},
Maybe: nil, Empty: []string{}, Credential: SecretEnv("APP_SECRET"), Classified: SecretEnv("APP_SECRET"),
}
},
Secret: func(path Path) bool {
return path.String() == "/dynamic/retired" || path.String() == "/classified"
},
Resolve: func(configuration *explanationConfig, resolution *Resolution) error {
Normalize(resolution, Keys("concurrency"), &configuration.Concurrency, 3,
"normalized\nconcurrency", Keys("labels"))
Normalize(resolution, Keys("equal"), &configuration.Equal, "same", "record equal value")
Normalize(resolution, Keys("derived_a"), &configuration.DerivedA, "DERIVED_EXPLAIN_A",
"derived cycle a", Keys("derived_b"))
Normalize(resolution, Keys("derived_b"), &configuration.DerivedB, "DERIVED_EXPLAIN_B",
"derived cycle b", Keys("derived_a"), Keys("dynamic"))
return nil
},
},
})
if err != nil {
t.Fatal(err)
}
concurrency, ok := result.Explain(Keys("concurrency"))
if !ok {
t.Fatal("concurrency was not explained")
}
if got := explanationValues(concurrency); !reflect.DeepEqual(got, []string{"4\n", "8\n", "1\n", "2\n", "3\n"}) {
t.Fatalf("concurrency history = %#v", got)
}
if got := historyValueLayers(concurrency); !reflect.DeepEqual(got, []Layer{LayerDefault, LayerBase, LayerOverlay, LayerCLI, LayerResolution}) {
t.Fatalf("concurrency layers = %#v", got)
}
if concurrency.History[4].Before.YAML != "2\n" || concurrency.History[4].Reasons[0] != "normalized\nconcurrency" ||
concurrency.History[4].Dependencies[0].String() != "/labels" {
t.Fatalf("resolution history = %#v", concurrency.History[4])
}
equal, _ := result.Explain(Keys("equal"))
if len(equal.History) != 5 {
t.Fatalf("equal writes were lost: %#v", equal.History)
}
for _, change := range equal.History[1:] {
if change.Before.YAML != "same\n" || change.After.YAML != "same\n" {
t.Fatalf("equal replacement = %#v", change)
}
}
limit, _ := result.Explain(Keys("limit"))
if got := explanationValues(limit); !reflect.DeepEqual(got, []string{"0\n", "wrong-before-overlay\n", "7\n"}) {
t.Fatalf("overridden invalid input was not retained: %#v", got)
}
removed, ok := result.Explain(Keys("dynamic", "retired"))
if !ok || removed.Effective.Present || len(removed.History) < 2 {
t.Fatalf("removed path = %#v, %v", removed, ok)
}
for _, change := range removed.History {
if strings.Contains(change.Before.YAML+change.After.YAML, "HISTORICAL_EXPLAIN_CANARY") {
t.Fatalf("historical secret escaped redaction: %#v", change)
}
}
if !removed.History[0].After.Redacted || removed.History[len(removed.History)-1].After.Present {
t.Fatalf("removed secret history = %#v", removed.History)
}
dynamic, _ := result.Explain(Keys("dynamic"))
if len(dynamic.History) < 3 || !dynamic.History[1].After.Redacted ||
strings.Contains(dynamic.History[1].After.YAML, "HISTORICAL_EXPLAIN_CANARY") {
t.Fatalf("container redaction history = %#v", dynamic.History)
}
items, _ := result.Explain(Keys("items"))
if len(items.History) != 4 || !strings.Contains(items.History[1].After.YAML, "removed") ||
strings.Contains(items.History[2].After.YAML, "removed") || !strings.Contains(items.History[3].After.YAML, "restored") {
t.Fatalf("atomic sequence history = %#v", items.History)
}
removedIndex, ok := result.Explain(Keys("items").Index(1))
if !ok || !removedIndex.History[1].Before.Present || removedIndex.History[1].After.Present ||
!removedIndex.History[2].After.Present {
t.Fatalf("sequence descendant history = %#v", removedIndex.History)
}
labels, _ := result.Explain(Keys("labels"))
if len(labels.History) != 4 || !strings.Contains(labels.Effective.YAML, "default: retained") ||
!strings.Contains(labels.Effective.YAML, "overlay: retained") || !strings.Contains(labels.Effective.YAML, "cli: retained") {
t.Fatalf("composite mapping history = %#v", labels)
}
if labels.Origin.Layer != LayerCLI || len(labels.Origin.Locations) != 3 {
t.Fatalf("composite effective origin = %#v", labels.Origin)
}
maybe, _ := result.Explain(Keys("maybe"))
empty, _ := result.Explain(Keys("empty"))
if !maybe.Effective.Present || maybe.Effective.YAML != "null\n" || !empty.Effective.Present || empty.Effective.YAML != "[]\n" {
t.Fatalf("null/empty snapshots = maybe %#v empty %#v", maybe.Effective, empty.Effective)
}
credential, _ := result.Explain(Keys("credential"))
if credential.SecretReference == nil || credential.SecretReference.Name != "APP_SECRET" || credential.Effective.YAML != "APP_SECRET\n" {
t.Fatalf("secret reference = %#v", credential)
}
classified, _ := result.Explain(Keys("classified"))
if classified.SecretReference != nil || !classified.Effective.Redacted ||
strings.Contains(classified.Effective.YAML, "APP_SECRET") {
t.Fatalf("classified secret reference = %#v", classified)
}
var derivedExplanations []Explanation
for _, path := range []Path{Keys("derived_a"), Keys("derived_b")} {
derived, _ := result.Explain(path)
if !derived.Effective.Redacted || strings.Contains(derived.Effective.YAML, "DERIVED_EXPLAIN") {
t.Fatalf("dependency closure at %s = %#v", path.String(), derived)
}
derivedExplanations = append(derivedExplanations, derived)
}
formatted := fmt.Sprintf("%v %#v %v", removed, credential, derivedExplanations)
encoded, marshalErr := json.Marshal(append([]Explanation{removed, credential, classified}, derivedExplanations...))
if marshalErr != nil || strings.Contains(formatted+string(encoded), "RESOLVED_EXPLAIN_CANARY") ||
strings.Contains(formatted+string(encoded), "HISTORICAL_EXPLAIN_CANARY") ||
strings.Contains(formatted+string(encoded), "DERIVED_EXPLAIN") {
t.Fatalf("public records disclosed a canary: %s %s", formatted, encoded)
}
concurrency.Path.segments[0].key = "mutated"
concurrency.History[4].Reasons[0] = "mutated"
concurrency.History[4].Dependencies[0].segments[0].key = "mutated"
result.Config.Concurrency = 99
again, _ := result.Explain(Keys("concurrency"))
if again.Path.String() != "/concurrency" || again.Effective.YAML != "3\n" ||
again.History[4].Reasons[0] != "normalized\nconcurrency" ||
again.History[4].Dependencies[0].String() != "/labels" {
t.Fatalf("returned explanation aliased internal history: %#v", again)
}
var first, second bytes.Buffer
if err := result.WriteExplanation(&first, Keys("concurrency")); err != nil {
t.Fatal(err)
}
if err := result.WriteExplanation(&second, Keys("concurrency")); err != nil || first.String() != second.String() {
t.Fatalf("nondeterministic rendering: %v\n%s\n%s", err, first.String(), second.String())
}
if strings.Contains(first.String(), "\nconcurrency\"") || !strings.Contains(first.String(), `"normalized\nconcurrency"`) {
t.Fatalf("reason controls were not escaped: %q", first.String())
}
unknownOutput := bytes.NewBufferString("unchanged")
if err := result.WriteExplanation(unknownOutput, Keys("unknown")); !errors.Is(err, errExplanationUnknownPath) || unknownOutput.String() != "unchanged" {
t.Fatalf("unknown writer = %q, %v", unknownOutput.String(), err)
}
if _, ok := result.Explain(Keys("items").Index(-1)); ok {
t.Fatal("malformed path was explained")
}
wantWriteError := errors.New("write failed")
if err := result.WriteExplanation(errorWriter{err: wantWriteError}, Keys("concurrency")); !errors.Is(err, wantWriteError) {
t.Fatalf("writer error = %v", err)
}
if err := result.WriteExplanation(shortWriter{}, Keys("concurrency")); !errors.Is(err, io.ErrShortWrite) {
t.Fatalf("short writer error = %v", err)
}
var invalidInput bytes.Buffer
if err := result.WriteExplanation(&invalidInput, Keys("limit")); err != nil ||
!strings.Contains(invalidInput.String(), "policy-validated input") ||
strings.Contains(invalidInput.String(), "wrong-before-overlay\n source: layer=\"base\"\n before (canonical typed)") {
t.Fatalf("historical representation rendering = %v\n%s", err, invalidInput.String())
}
}
func TestSecretsBootstrapLoadAndExplanationRemainSeparateAndSafe(t *testing.T) {
const name = "CONFIGKIT_EXPLAIN_BOOTSTRAP_TEST"
previous, present := os.LookupEnv(name)
if err := os.Unsetenv(name); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if present {
_ = os.Setenv(name, previous)
} else {
_ = os.Unsetenv(name)
}
})
directory := t.TempDir()
writeSecretTestFile(t, directory, name, "BOOTSTRAP_EXPLAIN_CANARY\n")
report, err := LoadSecretsDir(directory)
if err != nil || len(report.Entries()) != 1 || report.Entries()[0].Status != SecretFileLoaded {
t.Fatalf("bootstrap = %#v, %v", report, err)
}
type config struct {
Credential Secret `yaml:"credential"`
}
result, err := testLoad[config](Bytes("config.yml", []byte("credential: "+name+"\n")))
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
if err := result.WriteExplanation(&output, Keys("credential")); err != nil {
t.Fatal(err)
}
if !strings.Contains(output.String(), name) || strings.Contains(output.String(), "BOOTSTRAP_EXPLAIN_CANARY") {
t.Fatalf("explanation mixed bootstrap value into reference report: %s", output.String())
}
}
func TestHistoryNodeBudgetCountsSharedTreesOnce(t *testing.T) {
leaf := &document.Node{Kind: document.String, Text: "value"}
root := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "value", Value: leaf}}}
applications := []historyApplication{{layer: LayerBase, value: root}}
if !historyNodesWithin(2, root, root, root, applications, nil) {
t.Fatal("structurally shared history was counted more than once")
}
other := document.Clone(root)
if historyNodesWithin(3, root, other, root, applications, nil) {
t.Fatal("distinct retained nodes did not consume the budget")
}
}
func explanationValues(explanation Explanation) []string {
values := make([]string, len(explanation.History))
for index, change := range explanation.History {
values[index] = change.After.YAML
}
return values
}
func historyValueLayers(explanation Explanation) []Layer {
layers := make([]Layer, len(explanation.History))
for index, change := range explanation.History {
layers[index] = change.Origin.Layer
}
return layers
}
func TestExplanationReconcilesDefaultsAfterInputReplacement(t *testing.T) {
type cfg struct {
M map[string]int `yaml:"m"`
}
for _, base := range []string{"null", "replaced-text"} {
for _, cli := range []bool{false, true} {
t.Run(fmt.Sprintf("%s/cli=%v", base, cli), func(t *testing.T) {
root := t.TempDir()
main := writeImportFile(t, root, "main.yml", "m: "+base+"\n")
opts := Options[cfg]{Definition: Definition[cfg]{Defaults: func() cfg { return cfg{M: map[string]int{"a": 1}} }}}
if cli {
opts.Overrides = []Override{{Path: Keys("m"), Value: map[string]any{"b": 2}, Flag: "--m"}}
} else {
writeImportFile(t, root, "overlays/p.yml", "m: {b: 2}\n")
opts.Overlay = "p"
}
result, err := testLoad(File(main), opts)
if err != nil {
t.Fatal(err)
}
for _, path := range []Path{Keys("m"), Keys("m", "a")} {
explanation, ok := result.Explain(path)
if !ok {
t.Fatal("missing explanation")
}
last := explanation.History[len(explanation.History)-1]
if last.After != explanation.Effective || last.Origin.Operation != ChangeDecoding {
t.Fatalf("history does not reconcile final value: %#v", explanation)
}
info, _ := result.Metadata().Lookup(path)
if last.Origin.Layer != info.Origin.Layer {
t.Fatal("decoding lost effective origin")
}
}
})
}
}
}
func TestIntegerVersionInspectionRetainsInputAndReconcilesTypedValue(t *testing.T) {
type config struct {
Version string `yaml:"version"`
}
rule := &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error {
if version.Text != "4" {
t.Errorf("version = %#v", version)
}
return nil
}}
options := Options[config]{Definition: Definition[config]{
Defaults: func() config { return config{Version: "4"} },
Version: rule,
}}
first, err := testLoad(Bytes("version.yml", []byte("version: 4\n")), options)
if err != nil {
t.Fatal(err)
}
second, err := testLoad(Bytes("version.yml", []byte("version: 4\n")), options)
if err != nil {
t.Fatal(err)
}
if first.Config.Version != "4" || second.Config.Version != "4" {
t.Fatalf("configurations = %#v / %#v", first.Config, second.Config)
}
inputVersion, inputOK := documentAtPath(first.applications[0].value, Keys("version"))
finalVersion, finalOK := documentAtPath(first.final, Keys("version"))
if !inputOK || inputVersion.Kind != document.Number || inputVersion.Text != "4" ||
!finalOK || finalVersion.Kind != document.String || finalVersion.Text != "4" {
t.Fatalf("input/final = %#v / %#v", inputVersion, finalVersion)
}
info, ok := first.Metadata().Lookup(Keys("version"))
if !ok || !info.Explicit || info.Null || info.Origin.Layer != LayerBase || len(info.Origin.Locations) != 1 ||
info.Origin.Locations[0].SourceID != "source-1" || info.Origin.Locations[0].Line != 1 {
t.Fatalf("version metadata = %#v, %v", info, ok)
}
explanation, ok := first.Explain(Keys("version"))
if !ok || explanation.Effective.YAML != "'4'\n" {
t.Fatalf("version explanation = %#v, %v", explanation, ok)
}
foundInput, foundDecoding := false, false
for _, change := range explanation.History {
if change.After.YAML == "4\n" && change.Origin.Layer == LayerBase {
foundInput = true
}
if change.Before.YAML == "4\n" && change.After.YAML == "'4'\n" && change.Origin.Operation == ChangeDecoding {
foundDecoding = true
}
if change.Origin.Layer == LayerResolution {
t.Fatalf("conversion recorded as resolution: %#v", change)
}
}
if !foundInput || !foundDecoding {
t.Fatalf("version history = %#v", explanation.History)
}
paths := first.Metadata().HistoricalPaths()
foundPath := false
for _, path := range paths {
if path.String() == "/version" {
foundPath = true
}
}
if !foundPath || len(first.Metadata().History(Keys("version"))) < 2 {
t.Fatalf("historical version paths/history = %#v / %#v", paths, first.Metadata().History(Keys("version")))
}
var effective strings.Builder
if err := first.WriteEffective(&effective); err != nil || effective.String() != "version: '4'\n" {
t.Fatalf("effective output = %q, %v", effective.String(), err)
}
first.Config.Version = "mutated"
effective.Reset()
if err := first.WriteEffective(&effective); err != nil || effective.String() != "version: '4'\n" || second.Config.Version != "4" {
t.Fatalf("detached results = %q / %#v, %v", effective.String(), second.Config, err)
}
}
func TestSensitiveIntegerVersionStaysRedactedWithoutSecretLookup(t *testing.T) {
type config struct {
Version string `yaml:"version"`
}
lookups := 0
result, err := testLoadWithSecretLookup(Bytes("version.yml", []byte("version: 987654321\n")), func(string) (string, bool) {
lookups++
return "CREDENTIAL_CANARY", true
}, Options[config]{Definition: Definition[config]{
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
Secret: func(path Path) bool { return path.String() == "/version" },
}})
if err != nil || result.Config.Version != "987654321" || lookups != 0 {
t.Fatalf("result = %#v, lookups = %d, err = %v", result, lookups, err)
}
var effective strings.Builder
if err := result.WriteEffective(&effective); err != nil {
t.Fatal(err)
}
explanation, ok := result.Explain(Keys("version"))
formatted := effective.String() + fmt.Sprintf("%#v %#v", explanation, result.Metadata().History(Keys("version")))
if !ok || !explanation.Effective.Redacted || strings.Contains(formatted, "987654321") || strings.Contains(formatted, "CREDENTIAL_CANARY") {
t.Fatalf("sensitive inspection = %q / %#v", effective.String(), explanation)
}
}
func TestExplanationReplaysResolutionAdditionsAndOverlappingIntent(t *testing.T) {
type cfg struct {
M map[string]any `yaml:"m"`
}
for _, overlap := range []bool{false, true} {
t.Run(fmt.Sprint(overlap), func(t *testing.T) {
result, err := testLoad(Bytes("base", []byte("m: {old: 1}\n")), Options[cfg]{Definition: Definition[cfg]{Resolve: func(c *cfg, r *Resolution) error {
c.M["new"] = map[string]any{"child": int64(7)}
delete(c.M, "old")
r.Record(Keys("m", "new"), ChangeDerivedDefault, "add nested", Keys("m", "old"))
r.Record(Keys("m", "old"), ChangeNormalization, "remove old")
if overlap {
r.Record(Keys("m"), ChangeNormalization, "update container")
}
return nil
}}})
if err != nil {
t.Fatal(err)
}
for _, path := range []Path{Keys("m"), Keys("m", "new"), Keys("m", "new", "child"), Keys("m", "old")} {
x, ok := result.Explain(path)
if !ok {
t.Fatal("missing known history")
}
var count int
for _, v := range x.History {
if v.Origin.Layer == LayerResolution {
count++
if overlap && len(v.Reasons) != 3 {
t.Fatal("lost overlapping intent")
}
}
}
if overlap && count != 1 {
t.Fatalf("overlapping records applied %d times", count)
}
last := x.History[len(x.History)-1]
if last.After != x.Effective {
t.Fatalf("incorrect replay for %s: %#v", path, x)
}
}
})
}
}