Files
ConfigKit/internal/engine/destination_integration_test.go

501 lines
21 KiB
Go

package engine
import (
"bytes"
"fmt"
"reflect"
"strings"
"testing"
"testing/fstest"
)
func TestLayeredDestinationInheritanceUsesFinalSiblingScopes(t *testing.T) {
t.Parallel()
loaded, err := LoadLayered(
Input{Source: Bytes("main", []byte("parent:\n label: final\n settings:\n primary: {height: 4}\ncancelled:\n extends: null\n own: retained\n")), Destination: Keys("catalog", "entries")},
testSecretBinding(),
LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{
{Source: Bytes("parent", []byte("parent:\n label: lower\n settings:\n primary: {width: '10'}\n secondary: {extends: primary}\ncancelled:\n extends: parent\n own: retained\n")), Destination: Keys("catalog", "entries")},
{Source: Bytes("child", []byte("child:\n extends: parent\n settings:\n primary: {width: '20'}\n secondary: {extends: primary}\n")), Destination: Keys("catalog", "entries")},
}}}},
)
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
result := testLoaded(loaded)
entries := layeredMap(t, layeredMap(t, result.Config, "catalog"), "entries")
parent := layeredMap(t, entries, "parent")
child := layeredMap(t, entries, "child")
parentSettings := layeredMap(t, parent, "settings")
childSettings := layeredMap(t, child, "settings")
if parent["label"] != "final" || child["label"] != "final" {
t.Fatalf("higher-tier parent was not inherited after precedence: parent=%#v child=%#v", parent, child)
}
if layeredMapValue(t, parentSettings, "secondary", "width") != "10" ||
fmt.Sprint(layeredMapValue(t, parentSettings, "secondary", "height")) != "4" ||
layeredMapValue(t, childSettings, "secondary", "width") != "20" ||
fmt.Sprint(layeredMapValue(t, childSettings, "secondary", "height")) != "4" {
t.Fatalf("nested sibling scopes were not resolved at their destinations: parent=%#v child=%#v", parentSettings, childSettings)
}
cancelled := layeredMap(t, entries, "cancelled")
if len(cancelled) != 1 || cancelled["own"] != "retained" {
t.Fatalf("higher-tier cancellation did not suppress inheritance: %#v", cancelled)
}
if _, ok := result.Metadata().Lookup(Keys("catalog", "entries", "child", inheritanceKey)); ok {
t.Fatal("effective child retained its extends directive")
}
}
func TestLayeredDestinationInheritanceDoesNotSearchOtherCollections(t *testing.T) {
t.Parallel()
_, err := LoadLayered(
Input{Source: Bytes("main", []byte("parent: {value: foreign}\n")), Destination: Keys("catalog", "other")},
testSecretBinding(),
LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{{
Source: Bytes("child", []byte("child: {extends: parent}\n")),
Destination: Keys("catalog", "entries"),
}}}}},
)
if got := groupedErrorCode(t, err); got != CodeInheritanceMissing {
t.Fatalf("cross-collection parent lookup error = %s, want %s", got, CodeInheritanceMissing)
}
}
func TestLayeredMountedVersionProtectionCoversInheritanceAndUpperTiers(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
body string
rulePath Path
}{
{name: "inherited protected value", body: "parent: {version: v2}\nchild: {extends: parent}\n", rulePath: Keys("catalog", "entries", "child", "version")},
{name: "inherited protected ancestor", body: "parent: {schema: null}\nchild: {extends: parent}\n", rulePath: Keys("catalog", "entries", "child", "schema", "version")},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, err := LoadLayered(
Input{Source: NoSource()}, testSecretBinding(),
LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{Path: test.rulePath, Check: func(Version, *Validation) error { return nil }}},
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("underlay", []byte(test.body)), Destination: Keys("catalog", "entries")}}}},
},
)
if got := groupedErrorCode(t, err); got != CodeInheritanceVersion {
t.Fatalf("inheritance version protection code = %s", got)
}
})
}
t.Run("inheritance consumes a protected directive", func(t *testing.T) {
_, err := LoadLayered(
Input{Source: Bytes("main", []byte("parent: {value: inherited}\nchild: {extends: parent}\n")), Destination: Keys("catalog", "entries")},
testSecretBinding(),
LayeredOptions[map[string]any]{Definition: Definition[map[string]any]{Version: &VersionRule{
Path: Keys("catalog", "entries", "child", "extends"),
Check: func(version Version, _ *Validation) error {
if !version.Present || version.Text != "parent" {
t.Errorf("mounted protected directive value = %#v", version)
}
return nil
},
}}},
)
if got := groupedErrorCode(t, err); got != CodeInheritanceVersion {
t.Fatalf("consumed directive protection code = %s", got)
}
})
t.Run("inherited mapping ancestor without a version claim", func(t *testing.T) {
loaded, err := LoadLayered(
Input{Source: NoSource()}, testSecretBinding(),
LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{Path: Keys("catalog", "entries", "child", "schema", "version"), Check: func(Version, *Validation) error { return nil }}},
Underlays: []Underlay{{Inputs: []Input{{
Source: Bytes("underlay", []byte("parent: {schema: {name: ordinary}}\nchild: {extends: parent}\n")), Destination: Keys("catalog", "entries"),
}}}},
},
)
if err != nil {
t.Fatalf("nonclaiming inherited mapping ancestor was rejected: %v", err)
}
if got := layeredMapValue(t, testLoaded(loaded).Config, "catalog", "entries", "child", "schema", "name"); got != "ordinary" {
t.Fatalf("inherited non-version value = %#v", got)
}
})
for _, test := range []struct {
name string
body string
}{
{name: "overlay protected value", body: "catalog: {schema: {version: v2}}\n"},
{name: "overlay protected ancestor", body: "catalog: {schema: null}\n"},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
filesystem := NewFilesystem("overlay", fstest.MapFS{"overlays/test.yml": &fstest.MapFile{Data: []byte(test.body)}})
_, err := LoadLayered(
Input{Source: Bytes("main", []byte("schema: {version: v1}\n")), Destination: Keys("catalog")},
testSecretBinding(),
LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{Path: Keys("catalog", "schema", "version"), Check: func(version Version, _ *Validation) error {
if !version.Present || version.Text != "v1" {
t.Errorf("mounted main version = %#v", version)
}
return nil
}}},
Overlay: "test", OverlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem},
},
)
if got := groupedErrorCode(t, err); got != CodeOverlayVersion {
t.Fatalf("overlay version protection code = %s", got)
}
})
}
for _, test := range []struct {
name string
path Path
value any
}{
{name: "override protected value", path: Keys("catalog", "schema", "version"), value: "v2"},
{name: "override protected ancestor", path: Keys("catalog", "schema"), value: map[string]any{"version": "v2"}},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, err := LoadLayered(
Input{Source: Bytes("main", []byte("schema: {version: v1}\n")), Destination: Keys("catalog")},
testSecretBinding(),
LayeredOptions[map[string]any]{
Definition: Definition[map[string]any]{Version: &VersionRule{Path: Keys("catalog", "schema", "version"), Check: func(Version, *Validation) error { return nil }}},
Overrides: []Override{{Path: test.path, Value: test.value, Flag: "--config"}},
},
)
if got := groupedErrorCode(t, err); got != CodeOverrideVersion {
t.Fatalf("override version protection code = %s", got)
}
})
}
t.Run("unrelated inheritance under the protected mapping ancestor", func(t *testing.T) {
loaded, err := LoadLayered(
Input{Source: Bytes("main", []byte("schema: {version: v1}\nentries:\n parent: {value: inherited}\n child: {extends: parent}\n")), Destination: Keys("catalog")},
testSecretBinding(),
LayeredOptions[map[string]any]{Definition: Definition[map[string]any]{Version: &VersionRule{Path: Keys("catalog", "schema", "version"), Check: func(Version, *Validation) error { return nil }}}},
)
if err != nil {
t.Fatalf("unrelated mounted inheritance was rejected: %v", err)
}
child := layeredMapValue(t, testLoaded(loaded).Config, "catalog", "entries", "child", "value")
if child != "inherited" {
t.Fatalf("unrelated inheritance value = %#v", child)
}
})
}
func TestLayeredItemVersionProtectionTracksAssignedPeerIndexes(t *testing.T) {
t.Parallel()
definition := Definition[map[string]any]{
Version: &VersionRule{Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil }},
sequenceRegistrations: []sequenceRegistration{{path: Keys("catalog", "entries"), identityField: "id"}},
}
item := func(label string, value map[string]any) Input {
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}
}
options := func(inputs ...Input) LayeredOptions[map[string]any] {
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}
}
main := item("main", map[string]any{"id": "main", "schema": map[string]any{"version": "v1"}})
t.Run("same assigned main path is protected", func(t *testing.T) {
_, err := LoadLayered(main, testSecretBinding(), options(item("underlay", map[string]any{"id": "underlay", "schema": nil})))
if got := groupedErrorCode(t, err); got != CodeUnderlayVersion {
t.Fatalf("protected item claim code = %s, want %s", got, CodeUnderlayVersion)
}
})
t.Run("appended peer does not claim main path", func(t *testing.T) {
loaded, err := LoadLayered(main, testSecretBinding(), options(
item("first", map[string]any{"id": "first", "schema": map[string]any{"other": "value"}}),
item("peer", map[string]any{"id": "peer", "schema": nil}),
))
if err != nil {
t.Fatalf("unrelated appended peer rejected: %v", err)
}
entries := layeredMapValue(t, testLoaded(loaded).Config, "catalog", "entries").([]any)
if len(entries) != 1 || entries[0].(map[string]any)["id"] != "main" {
t.Fatalf("main item projection = %#v", entries)
}
})
}
type destinationDecodeProbe string
var destinationDecodeProbeValues []string
func (value *destinationDecodeProbe) UnmarshalText(text []byte) error {
destinationDecodeProbeValues = append(destinationDecodeProbeValues, string(text))
*value = destinationDecodeProbe(text)
return nil
}
func (value destinationDecodeProbe) MarshalText() ([]byte, error) {
return []byte(value), nil
}
type destinationLifecycleConfig struct {
Defaulted string `yaml:"defaulted"`
Tenant struct {
Tagged string `yaml:"tagged" configkit:"secret"`
Dynamic string `yaml:"dynamic"`
Credential Secret `yaml:"credential"`
Derived string `yaml:"derived"`
Decoded destinationDecodeProbe `yaml:"decoded"`
} `yaml:"tenant"`
}
func TestLayeredDestinationLifecycleAndSensitiveInspectionUseFinalShape(t *testing.T) {
const (
oldTagged = "SUPERSEDED-TAGGED-CANARY"
newTagged = "CURRENT-TAGGED-CANARY"
oldDynamic = "SUPERSEDED-DYNAMIC-CANARY"
newDynamic = "CURRENT-DYNAMIC-CANARY"
credential = "RESOLVED-CREDENTIAL-CANARY"
secretName = "DESTINATION_SECRET_NAME"
decodeValue = "decoded-once"
)
type calls struct{ defaults, resolve, lookup, validate int }
newLoad := func(mounted bool, observed *calls) (*testResult[destinationLifecycleConfig], error) {
mainBody := "tagged: " + newTagged + "\ndynamic: " + newDynamic + "\ncredential: " + secretName + "\ndecoded: " + decodeValue + "\n"
underlayBody := "tagged: " + oldTagged + "\ndynamic: " + oldDynamic + "\ncredential: OLD_DESTINATION_SECRET_NAME\ndecoded: old\n"
if !mounted {
mainBody = "tenant:\n" + indentDestinationBody(mainBody)
underlayBody = "tenant:\n" + indentDestinationBody(underlayBody)
}
main := loadInput{source: Bytes("main", []byte(mainBody))}
underlay := loadInput{source: Bytes("underlay", []byte(underlayBody))}
if mounted {
main.destination = Keys("tenant")
underlay.destination = Keys("tenant")
}
definition := Definition[destinationLifecycleConfig]{
Defaults: func() destinationLifecycleConfig {
observed.defaults++
return destinationLifecycleConfig{Defaulted: "from-default"}
},
Resolve: func(configuration *destinationLifecycleConfig, resolution *Resolution) error {
observed.resolve++
configuration.Tenant.Derived = configuration.Tenant.Dynamic
resolution.Record(Keys("tenant", "derived"), ChangeNormalization, "derive from mounted input", Keys("tenant", "dynamic"))
return nil
},
Validate: func(configuration *destinationLifecycleConfig, _ *Validation) {
observed.validate++
if configuration.Tenant.Decoded != destinationDecodeProbe(decodeValue) || configuration.Tenant.Dynamic != newDynamic {
t.Errorf("validation saw a non-final mounted configuration: %#v", configuration.Tenant)
}
},
Secret: func(path Path) bool { return path.String() == "/tenant/dynamic" },
}
loaded, err := loadGroupedWithSecretLookup(loadRequest[destinationLifecycleConfig]{
main: main, underlays: []loadGroup{{inputs: []loadInput{underlay}}}, definition: definition,
}, testSecretBinding(), func(name string) (string, bool) {
observed.lookup++
if name != secretName {
return "", false
}
return credential, true
})
return testLoaded(loaded), err
}
var observed calls
destinationDecodeProbeValues = nil
mounted, err := newLoad(true, &observed)
if err != nil {
if loadError, ok := err.(*LoadError); ok {
t.Fatalf("mounted load: %#v", loadError.Diagnostics())
}
t.Fatalf("mounted load: %v", err)
}
if observed != (calls{defaults: 1, resolve: 1, lookup: 1, validate: 1}) {
t.Fatalf("mounted lifecycle counts = %#v", observed)
}
sawFinalDecode := false
for _, value := range destinationDecodeProbeValues {
if value == decodeValue {
sawFinalDecode = true
} else if value != "" {
t.Fatalf("decoder observed a non-final mounted value %q: %#v", value, destinationDecodeProbeValues)
}
}
if !sawFinalDecode {
t.Fatal("final mounted value was not decoded")
}
if mounted.Config.Defaulted != "from-default" || mounted.Config.Tenant.Tagged != newTagged || mounted.Config.Tenant.Dynamic != newDynamic ||
mounted.Config.Tenant.Derived != newDynamic || mounted.Config.Tenant.Decoded != destinationDecodeProbe(decodeValue) ||
mounted.Config.Tenant.Credential.Value() != credential {
t.Fatalf("mounted final configuration = %#v", mounted.Config)
}
for _, path := range []Path{Keys("tenant", "tagged"), Keys("tenant", "dynamic"), Keys("tenant", "derived")} {
if !mounted.Inspection.secrets[path.String()] {
t.Errorf("effective sensitivity missing at %s: %#v", path, mounted.Inspection.secrets)
}
}
for _, path := range []Path{Keys("tenant", "tagged"), Keys("tenant", "dynamic"), Keys("tenant", "derived")} {
explanation, ok := mounted.Explain(path)
if !ok || !explanation.Effective.Redacted {
t.Errorf("effective explanation at %s = %#v, %v", path, explanation, ok)
continue
}
for _, change := range explanation.History {
if strings.Contains(change.Before.YAML+change.After.YAML, oldTagged) ||
strings.Contains(change.Before.YAML+change.After.YAML, newTagged) ||
strings.Contains(change.Before.YAML+change.After.YAML, oldDynamic) ||
strings.Contains(change.Before.YAML+change.After.YAML, newDynamic) {
t.Errorf("classified history leaked a canary at %s: %#v", path, change)
}
}
var rendered bytes.Buffer
if err := mounted.WriteExplanation(&rendered, path); err != nil || strings.Contains(rendered.String(), "CANARY") {
t.Errorf("safe explanation at %s = %q, %v", path, rendered.String(), err)
}
}
var effective bytes.Buffer
if err := mounted.WriteEffective(&effective); err != nil {
t.Fatal(err)
}
for _, canary := range []string{oldTagged, newTagged, oldDynamic, newDynamic, credential} {
if strings.Contains(effective.String(), canary) {
t.Fatalf("effective rendering exposed %q: %s", canary, effective.String())
}
}
if serializedConfig := fmt.Sprintf("%#v", mounted.Config); strings.Contains(serializedConfig, credential) {
t.Fatalf("configuration formatting exposed the resolved credential: %s", serializedConfig)
}
for _, path := range []Path{Keys("tenant", "tagged"), Keys("tenant", "dynamic")} {
if history := fmt.Sprintf("%#v", mounted.Metadata().History(path)); strings.Contains(history, "CANARY") {
t.Fatalf("metadata history at %s exposed a canary: %s", path, history)
}
}
var authoredCalls calls
destinationDecodeProbeValues = nil
authored, err := newLoad(false, &authoredCalls)
if err != nil {
t.Fatalf("explicitly wrapped load: %v", err)
}
if authoredCalls != (calls{defaults: 1, resolve: 1, lookup: 1, validate: 1}) {
t.Fatalf("authored lifecycle counts = %#v", authoredCalls)
}
sawFinalDecode = false
for _, value := range destinationDecodeProbeValues {
if value == decodeValue {
sawFinalDecode = true
} else if value != "" {
t.Fatalf("authored decoder observed a non-final value %q: %#v", value, destinationDecodeProbeValues)
}
}
if !sawFinalDecode {
t.Fatal("final authored value was not decoded")
}
if !reflect.DeepEqual(mounted.Config, authored.Config) {
t.Fatalf("mounted config %#v differs from explicitly wrapped config %#v", mounted.Config, authored.Config)
}
differences, err := Diff(mounted.Inspection, authored.Inspection)
if err != nil || len(differences) != 0 {
t.Fatalf("mounted versus authored diff = %#v, %v", differences, err)
}
}
func TestLayeredDestinationDiffUsesBilateralSensitivity(t *testing.T) {
t.Parallel()
type config struct {
Tenant struct {
Dynamic string `yaml:"dynamic"`
Derived string `yaml:"derived"`
} `yaml:"tenant"`
}
load := func(value string, classify bool) *testResult[config] {
t.Helper()
loaded, err := loadGroupedWithSecretLookup(loadRequest[config]{
main: loadInput{source: Bytes("input", []byte("dynamic: "+value+"\n")), destination: Keys("tenant")},
definition: Definition[config]{
Resolve: func(configuration *config, resolution *Resolution) error {
configuration.Tenant.Derived = configuration.Tenant.Dynamic
resolution.Record(Keys("tenant", "derived"), ChangeNormalization, "derive from dynamic input", Keys("tenant", "dynamic"))
return nil
},
Secret: func(path Path) bool { return classify && path.String() == "/tenant/dynamic" },
},
}, testSecretBinding(), func(string) (string, bool) { return "", false })
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
return testLoaded(loaded)
}
left := load("LEFT-DIFF-CANARY", true)
right := load("RIGHT-DIFF-CANARY", false)
differences, err := Diff(left.Inspection, right.Inspection)
if err != nil {
t.Fatal(err)
}
if len(differences) != 2 {
t.Fatalf("differences = %#v", differences)
}
seen := make(map[string]bool)
for _, difference := range differences {
path := difference.Path.String()
seen[path] = true
if !difference.Left.Redacted || !difference.Right.Redacted || !difference.HiddenChanged ||
strings.Contains(difference.Left.YAML+difference.Right.YAML, "DIFF-CANARY") {
t.Errorf("bilaterally classified difference at %s = %#v", path, difference)
}
}
for _, path := range []string{"/tenant/dynamic", "/tenant/derived"} {
if !seen[path] || !left.Inspection.secrets[path] || right.Inspection.secrets[path] {
t.Errorf("classification/difference at %s: seen=%v left=%v right=%v", path, seen[path], left.Inspection.secrets[path], right.Inspection.secrets[path])
}
}
}
func TestLayeredDestinationDecodeFailureReturnsNoResultOrLaterCallbacks(t *testing.T) {
t.Parallel()
type config struct {
Tenant struct {
Count int `yaml:"count"`
} `yaml:"tenant"`
}
calls := struct{ defaults, resolve, lookup, validate int }{}
loaded, err := loadGroupedWithSecretLookup(loadRequest[config]{
main: loadInput{source: Bytes("invalid", []byte("count: not-an-integer\n")), destination: Keys("tenant")},
definition: Definition[config]{
Defaults: func() config { calls.defaults++; return config{} },
Resolve: func(*config, *Resolution) error { calls.resolve++; return nil },
Validate: func(*config, *Validation) { calls.validate++ },
},
}, testSecretBinding(), func(string) (string, bool) { calls.lookup++; return "", false })
if loaded != nil || err == nil {
t.Fatalf("mounted decode failure returned a usable result: %#v, %v", loaded, err)
}
if calls.defaults != 1 || calls.resolve != 0 || calls.lookup != 0 || calls.validate != 0 {
t.Fatalf("callbacks after mounted decode failure = %#v", calls)
}
if got := groupedErrorCode(t, err); got != CodeDecodeType {
t.Fatalf("mounted decode failure code = %s", got)
}
}
func indentDestinationBody(body string) string {
var result strings.Builder
for _, line := range strings.Split(strings.TrimSuffix(body, "\n"), "\n") {
result.WriteString(" ")
result.WriteString(line)
result.WriteByte('\n')
}
return result.String()
}