595 lines
25 KiB
Go
595 lines
25 KiB
Go
package configkit_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
type sourceVersionModeReader struct {
|
|
*strings.Reader
|
|
reads int
|
|
}
|
|
|
|
func TestFacadeSourceVersionEvidenceRedactsCollidingShapes(t *testing.T) {
|
|
t.Parallel()
|
|
type header struct {
|
|
Version string `yaml:"version"`
|
|
}
|
|
type secretHeader struct {
|
|
Version string `yaml:"version" configkit:"secret"`
|
|
}
|
|
type configuration struct {
|
|
Meta map[string]header `yaml:"meta"`
|
|
Data struct {
|
|
Meta map[string]secretHeader `yaml:"meta"`
|
|
} `yaml:"data"`
|
|
}
|
|
path, err := configkit.ParsePath("/meta/0/version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, reverse := range []bool{false, true} {
|
|
groups := []configkit.Underlay{
|
|
{Inputs: []configkit.Input{{
|
|
Source: configkit.Bytes("mapping", []byte("meta: {\"0\": {version: PRIVATE-HEADER-CANARY}}\n")),
|
|
Destination: configkit.Keys("data"),
|
|
}}},
|
|
{Inputs: []configkit.Input{{
|
|
Source: configkit.Bytes("sequence", []byte("meta: [{version: another-header}]\n")),
|
|
Destination: configkit.Keys("data"),
|
|
}}},
|
|
}
|
|
if reverse {
|
|
groups[0], groups[1] = groups[1], groups[0]
|
|
}
|
|
loaded, err := configkit.LoadLayered(configkit.Input{
|
|
Source: configkit.Bytes("main", []byte("meta: {\"0\": {version: main}}\ndata: {meta: null}\n")),
|
|
}, configkit.LayeredOptions[configuration]{
|
|
Definition: configkit.Definition[configuration]{Version: &configkit.VersionRule{
|
|
Path: path, Check: func(configkit.Version, *configkit.Validation) error { return nil },
|
|
}},
|
|
SourceVersionMode: configkit.SourceVersionModeRequired, Underlays: groups,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if loaded.Config.Meta["0"].Version != "main" || loaded.Config.Data.Meta != nil {
|
|
t.Fatalf("reverse=%v: final payload changed: %#v", reverse, loaded.Config)
|
|
}
|
|
foundMapping := false
|
|
for _, source := range loaded.Sources() {
|
|
if source.Label != "mapping" {
|
|
continue
|
|
}
|
|
foundMapping = true
|
|
if check := source.VersionCheck; check == nil || !check.Version.Present || !check.Redacted || check.Version.Text != "" {
|
|
t.Errorf("reverse=%v: schema-tagged header was not redacted", reverse)
|
|
}
|
|
}
|
|
if !foundMapping {
|
|
t.Fatal("missing mapping source record")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (reader *sourceVersionModeReader) Read(data []byte) (int, error) {
|
|
reader.reads++
|
|
return reader.Reader.Read(data)
|
|
}
|
|
|
|
func TestFacadeSourceVersionModesAcrossOrdinaryAndLayeredLoads(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var checked []configkit.Version
|
|
definition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("meta", "version"),
|
|
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
checked = append(checked, version)
|
|
return nil
|
|
},
|
|
}}
|
|
ordinary, err := configkit.Load(configkit.Bytes("ordinary", []byte("meta: {version: ordinary-v}\n")), configkit.Options[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
if err != nil || ordinary == nil {
|
|
t.Fatalf("ordinary enabled load = %#v, %v", ordinary, err)
|
|
}
|
|
if got := ordinary.Config["meta"].(map[string]any)["version"]; got != "ordinary-v" {
|
|
t.Fatalf("main declaration was not retained: %#v", ordinary.Config)
|
|
}
|
|
|
|
directory := t.TempDir()
|
|
mainPath := filepath.Join(directory, "main.yml")
|
|
if err := os.WriteFile(mainPath, []byte("meta: {version: main-v}\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
layered, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(mainPath)}, configkit.LayeredOptions[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeRequired,
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{
|
|
Source: configkit.Mapping("underlay", map[string]any{
|
|
"meta": map[string]any{"version": "underlay-v"}, "fallback": "present",
|
|
}),
|
|
Destination: configkit.Keys("defaults"),
|
|
}}}},
|
|
})
|
|
if err != nil || layered == nil {
|
|
t.Fatalf("layered enabled load = %#v, %v", layered, err)
|
|
}
|
|
defaults, ok := layered.Config["defaults"].(map[string]any)
|
|
if !ok || defaults["fallback"] != "present" {
|
|
t.Fatalf("layered underlay payload = %#v", layered.Config["defaults"])
|
|
}
|
|
if meta, ok := defaults["meta"].(map[string]any); !ok || len(meta) != 0 {
|
|
t.Fatalf("accepted underlay declaration was not consumed: %#v", defaults["meta"])
|
|
}
|
|
if len(checked) != 3 {
|
|
t.Fatalf("checker calls across separate mode uses = %#v", checked)
|
|
}
|
|
for _, want := range []string{"ordinary-v", "main-v", "underlay-v"} {
|
|
found := false
|
|
for _, version := range checked {
|
|
found = found || version.Present && version.Text == want
|
|
}
|
|
if !found {
|
|
t.Errorf("checked versions %#v do not include %q", checked, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionModeZeroPreservesDefaultBehavior(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
checks := 0
|
|
result, err := configkit.Load(configkit.Bytes("default", []byte("version: v1\n")), configkit.Options[map[string]any]{
|
|
Definition: configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"),
|
|
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
checks++
|
|
if version != (configkit.Version{Present: true, Text: "v1"}) {
|
|
t.Errorf("default version = %#v", version)
|
|
}
|
|
return nil
|
|
},
|
|
}},
|
|
})
|
|
if err != nil || result == nil || result.Config["version"] != "v1" || checks != 1 {
|
|
t.Fatalf("zero-mode load = %#v, %v; checks = %d", result, err, checks)
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionModeValidationPrecedesSourceReads(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
validDefinition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil },
|
|
}}
|
|
for _, test := range []struct {
|
|
name string
|
|
mode configkit.SourceVersionMode
|
|
def configkit.Definition[map[string]any]
|
|
layered bool
|
|
}{
|
|
{name: "unknown ordinary mode", mode: configkit.SourceVersionMode(255), def: validDefinition},
|
|
{name: "ordinary mode requires checker", mode: configkit.SourceVersionModeIfPresent},
|
|
{name: "unknown layered mode", mode: configkit.SourceVersionMode(255), def: validDefinition, layered: true},
|
|
{name: "layered mode requires checker", mode: configkit.SourceVersionModeRequired, layered: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
reader := &sourceVersionModeReader{Reader: strings.NewReader("version: v1\n")}
|
|
source := configkit.Reader("version.yml", reader)
|
|
var err error
|
|
if test.layered {
|
|
_, err = configkit.LoadLayered(configkit.Input{Source: source}, configkit.LayeredOptions[map[string]any]{
|
|
Definition: test.def, SourceVersionMode: test.mode,
|
|
})
|
|
} else {
|
|
_, err = configkit.Load(source, configkit.Options[map[string]any]{
|
|
Definition: test.def, SourceVersionMode: test.mode,
|
|
})
|
|
}
|
|
var loadError *configkit.LoadError
|
|
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeOptionsInvalid {
|
|
t.Fatalf("invalid mode error = %#v", err)
|
|
}
|
|
if reader.reads != 0 {
|
|
t.Fatalf("invalid options read source %d times", reader.reads)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionDefinitionCanBeReusedWithDifferentModes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
checks := 0
|
|
definition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"),
|
|
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
checks++
|
|
if version.Present {
|
|
t.Errorf("missing declaration reached checker as %#v", version)
|
|
}
|
|
return nil
|
|
},
|
|
}}
|
|
source := configkit.Bytes("reused", []byte("value: retained\n"))
|
|
ifPresent, err := configkit.Load(source, configkit.Options[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
if err != nil || ifPresent == nil || checks != 1 {
|
|
t.Fatalf("if-present load = %#v, %v; checks = %d", ifPresent, err, checks)
|
|
}
|
|
required, err := configkit.Load(source, configkit.Options[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeRequired,
|
|
})
|
|
var loadError *configkit.LoadError
|
|
if required != nil || !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeVersionError {
|
|
t.Fatalf("required load = %#v, %v", required, err)
|
|
}
|
|
if checks != 1 {
|
|
t.Fatalf("required absence reached checker: calls = %d", checks)
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionEvidenceIsSanitizedAndDetached(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const canary = "PRIVATE-SOURCE-VERSION-CANARY"
|
|
directory := t.TempDir()
|
|
path := filepath.Join(directory, "main.yml")
|
|
if err := os.WriteFile(path, []byte("version: "+canary+"\nvalue: visible\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var callbackSource configkit.SourceInfo
|
|
var callbackContext configkit.ResourceContext
|
|
definition := configkit.Definition[map[string]any]{
|
|
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }},
|
|
Secret: func(candidate configkit.Path) bool { return candidate.String() == "/service/version" },
|
|
Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error {
|
|
var found bool
|
|
callbackSource, found = resolution.Source("source-1")
|
|
if !found {
|
|
t.Fatal("resolution callback could not query the acquired source")
|
|
}
|
|
if callbackSource.VersionCheck == nil || !callbackSource.VersionCheck.Redacted || callbackSource.VersionCheck.Version.Text != "" {
|
|
t.Fatalf("callback source did not conservatively redact evidence: %#v", callbackSource.VersionCheck)
|
|
}
|
|
callbackContext, found = resolution.SourceContext("source-1")
|
|
if !found || callbackContext.FS == nil || callbackContext.BaseDir != "." {
|
|
t.Fatalf("callback source context = %#v, found=%v", callbackContext, found)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
loaded, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(path), Destination: configkit.Keys("service")}, configkit.LayeredOptions[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
if err != nil || loaded == nil {
|
|
t.Fatalf("LoadLayered = %#v, %v", loaded, err)
|
|
}
|
|
if callbackSource.VersionCheck == nil || !callbackSource.VersionCheck.Redacted || callbackSource.VersionCheck.Version.Text != "" {
|
|
t.Fatalf("callback evidence lost its conservative snapshot: %#v", callbackSource.VersionCheck)
|
|
}
|
|
callbackSource.VersionCheck.Path = configkit.Keys("mutated-callback")
|
|
callbackSource.VersionCheck.Version.Text = canary
|
|
callbackSource.VersionCheck.Location.Line = 98
|
|
finalSources := loaded.Sources()
|
|
if len(finalSources) != 1 || finalSources[0].Path != path || finalSources[0].VersionCheck == nil {
|
|
t.Fatalf("final sources = %#v", finalSources)
|
|
}
|
|
check := finalSources[0].VersionCheck
|
|
if check.Path.String() != "/version" || check.Version != (configkit.Version{Present: true}) || !check.Redacted ||
|
|
check.Location == nil || check.Location.SourceID != finalSources[0].ID || check.Location.Line != 1 {
|
|
t.Fatalf("final classified evidence = %#v", check)
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if callbackContext.FS == nil || callbackContext.BaseDir != "." {
|
|
t.Fatalf("resource metadata changed after removing the source: %#v", callbackContext)
|
|
}
|
|
check.Path = configkit.Keys("mutated")
|
|
check.Location.Line = 99
|
|
check.Redacted = false
|
|
check.Version.Text = "mutated"
|
|
repeated := loaded.Sources()
|
|
if len(repeated) != 1 || repeated[0].VersionCheck.Path.String() != "/version" ||
|
|
repeated[0].VersionCheck.Location.Line != 1 || !repeated[0].VersionCheck.Redacted ||
|
|
repeated[0].VersionCheck.Version.Text != "" {
|
|
t.Fatalf("source evidence was aliased or re-read after source removal: %#v", repeated)
|
|
}
|
|
|
|
publicText := "PUBLIC-SOURCE-VERSION"
|
|
var publicCallback configkit.SourceInfo
|
|
public, err := configkit.Load(configkit.Bytes("public", []byte("version: "+publicText+"\n")), configkit.Options[map[string]any]{
|
|
SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
Definition: configkit.Definition[map[string]any]{
|
|
Version: definition.Version,
|
|
Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error {
|
|
publicCallback, _ = resolution.Source("source-1")
|
|
return nil
|
|
},
|
|
},
|
|
})
|
|
if err != nil || public == nil || len(public.Sources()) != 1 || public.Sources()[0].VersionCheck == nil {
|
|
t.Fatalf("unclassified source evidence = %#v, %v", public, err)
|
|
}
|
|
if got := public.Sources()[0].VersionCheck; got.Redacted || got.Version.Text != publicText || !got.Version.Present {
|
|
t.Fatalf("unclassified evidence = %#v", got)
|
|
}
|
|
if publicCallback.VersionCheck == nil || !publicCallback.VersionCheck.Redacted || publicCallback.VersionCheck.Version.Text != "" {
|
|
t.Fatalf("unclassified callback evidence was not initially conservative: %#v", publicCallback.VersionCheck)
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionEvidenceOnFailedLoadsAndSyntheticSources(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
definition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil },
|
|
}}
|
|
t.Run("early import acquisition failure", func(t *testing.T) {
|
|
const canary = "EARLY-ACQUISITION-SOURCE-VERSION-CANARY"
|
|
checked := 0
|
|
earlyDefinition := configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { checked++; return nil },
|
|
}}
|
|
path := filepath.Join(t.TempDir(), "main.yml")
|
|
contents := "version: " + canary + "\nconfigkit:\n imports:\n - path: missing.yml\n"
|
|
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(path), Imports: true}, configkit.LayeredOptions[map[string]any]{
|
|
Definition: earlyDefinition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
var failure *configkit.LoadError
|
|
if !errors.As(err, &failure) || len(failure.Sources()) != 1 {
|
|
t.Fatalf("early acquisition failure sources = %#v, error %v", failure, err)
|
|
}
|
|
if checked != 1 {
|
|
t.Fatalf("main declaration checker calls = %d; error %v", checked, err)
|
|
}
|
|
check := failure.Sources()[0].VersionCheck
|
|
if check == nil || !check.Redacted || !check.Version.Present || check.Version.Text != "" {
|
|
t.Fatalf("early acquisition evidence was not conservatively redacted: %#v; sources %#v", check, failure.Sources())
|
|
}
|
|
if strings.Contains(err.Error(), canary) {
|
|
t.Fatalf("early acquisition failure disclosed source-version canary: %v", err)
|
|
}
|
|
})
|
|
_, err := configkit.Load(configkit.Bytes("malformed", []byte("version: true\n")), configkit.Options[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
var loadError *configkit.LoadError
|
|
if !errors.As(err, &loadError) {
|
|
t.Fatalf("malformed version error = %v", err)
|
|
}
|
|
sources := loadError.Sources()
|
|
if len(sources) != 1 || sources[0].VersionCheck == nil || !sources[0].VersionCheck.Redacted ||
|
|
!sources[0].VersionCheck.Version.Present || sources[0].VersionCheck.Version.Text != "" || sources[0].VersionCheck.Path.String() != "/version" {
|
|
t.Fatalf("failed-load source evidence = %#v", sources)
|
|
}
|
|
sources[0].VersionCheck.Path = configkit.Keys("mutated-error")
|
|
sources[0].VersionCheck.Version.Text = "mutated"
|
|
sources[0].VersionCheck.Location.Line = 99
|
|
if again := loadError.Sources()[0].VersionCheck; again.Path.String() != "/version" || again.Version.Text != "" || again.Location.Line == 99 {
|
|
t.Fatalf("LoadError source evidence was aliased: %#v", again)
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
def configkit.Definition[map[string]any]
|
|
canary string
|
|
}{
|
|
{
|
|
name: "resolver failure",
|
|
canary: "RESOLVER-SOURCE-VERSION-CANARY",
|
|
def: configkit.Definition[map[string]any]{Version: definition.Version, Resolve: func(*map[string]any, *configkit.Resolution) error {
|
|
return errors.New("resolver failed")
|
|
}},
|
|
},
|
|
{
|
|
name: "validator failure",
|
|
canary: "VALIDATOR-SOURCE-VERSION-CANARY",
|
|
def: configkit.Definition[map[string]any]{Version: definition.Version, Validate: func(_ *map[string]any, validation *configkit.Validation) {
|
|
validation.Error(configkit.Keys("value"), "invalid value")
|
|
}},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
_, err := configkit.Load(configkit.Bytes("late-failure", []byte("version: "+test.canary+"\nvalue: configured\n")), configkit.Options[map[string]any]{
|
|
Definition: test.def, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
})
|
|
var failure *configkit.LoadError
|
|
if !errors.As(err, &failure) || len(failure.Sources()) != 1 {
|
|
t.Fatalf("late failure sources = %#v, error %v", failure, err)
|
|
}
|
|
if strings.Contains(err.Error(), test.canary) {
|
|
t.Fatalf("late failure disclosed source-version canary: %v", err)
|
|
}
|
|
check := failure.Sources()[0].VersionCheck
|
|
if check == nil || !check.Redacted || !check.Version.Present || check.Version.Text != "" {
|
|
t.Fatalf("late-failure evidence was not conservatively redacted: %#v", check)
|
|
}
|
|
})
|
|
}
|
|
|
|
defaultResult, err := configkit.Load(configkit.Bytes("default", []byte("version: 7\n")), configkit.Options[map[string]any]{})
|
|
if err != nil || defaultResult == nil || len(defaultResult.Sources()) != 1 || defaultResult.Sources()[0].VersionCheck != nil {
|
|
t.Fatalf("default source version check = %#v, %v", defaultResult, err)
|
|
}
|
|
|
|
withCLI, err := configkit.Load(configkit.Bytes("main", []byte("version: 8\nvalue: old\n")), configkit.Options[map[string]any]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
Overrides: []configkit.Override{{Path: configkit.Keys("value"), Value: "new", Flag: "--value"}},
|
|
})
|
|
if err != nil || withCLI == nil {
|
|
t.Fatalf("enabled load with CLI source = %#v, %v", withCLI, err)
|
|
}
|
|
sawCLI := false
|
|
for _, source := range withCLI.Sources() {
|
|
if source.Kind == configkit.SourceKindCLI && source.VersionCheck != nil {
|
|
t.Fatalf("synthetic CLI source acquired declaration evidence: %#v", source)
|
|
}
|
|
sawCLI = sawCLI || source.Kind == configkit.SourceKindCLI
|
|
}
|
|
if !sawCLI {
|
|
t.Fatalf("CLI source record missing: %#v", withCLI.Sources())
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionEvidencePreservesAbsenceAndNull(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var checked []configkit.Version
|
|
loaded, err := configkit.LoadLayered(configkit.Input{Source: configkit.Bytes("main", []byte("version: 17\n"))}, configkit.LayeredOptions[map[string]any]{
|
|
SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
Definition: configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"),
|
|
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
checked = append(checked, version)
|
|
return nil
|
|
},
|
|
}},
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{
|
|
{Source: configkit.Bytes("absent", []byte("value: visible\n")), Destination: configkit.Keys("absent")},
|
|
{Source: configkit.Bytes("null", []byte("version: null\nvalue: visible\n")), Destination: configkit.Keys("nullable")},
|
|
}}},
|
|
})
|
|
if err != nil || loaded == nil {
|
|
t.Fatalf("LoadLayered = %#v, %v", loaded, err)
|
|
}
|
|
if len(checked) != 2 || !checked[0].Present || checked[0].Text != "17" ||
|
|
!checked[1].Present || !checked[1].Null {
|
|
t.Fatalf("checker calls = %#v; absent contribution must skip while null is checked", checked)
|
|
}
|
|
sources := loaded.Sources()
|
|
if len(sources) != 3 || sources[1].VersionCheck == nil || sources[2].VersionCheck == nil {
|
|
t.Fatalf("source version evidence = %#v", sources)
|
|
}
|
|
absent := sources[1].VersionCheck
|
|
if absent.Version.Present || absent.Path.String() != "/version" || absent.Location == nil || absent.Redacted {
|
|
t.Fatalf("optional omission evidence = %#v", absent)
|
|
}
|
|
null := sources[2].VersionCheck
|
|
if !null.Version.Present || !null.Version.Null || null.Version.Text != "" || null.Path.String() != "/version" {
|
|
t.Fatalf("null declaration evidence = %#v", null)
|
|
}
|
|
}
|
|
|
|
func TestFacadeSourceVersionLifecycleKeepsConsumedHeadersOutOfPayloadHistory(t *testing.T) {
|
|
type serviceConfig struct {
|
|
Version uint64 `yaml:"version"`
|
|
Count int `yaml:"count"`
|
|
Enabled bool `yaml:"enabled"`
|
|
Nullable *string `yaml:"nullable"`
|
|
Revision string `yaml:"revision"`
|
|
Defaulted string `yaml:"defaulted"`
|
|
Resolved string `yaml:"resolved"`
|
|
Credential configkit.Secret `yaml:"credential"`
|
|
}
|
|
type configuration struct {
|
|
Service serviceConfig `yaml:"service"`
|
|
}
|
|
t.Setenv("CONFIGKIT_LIFECYCLE_SECRET", "lifecycle-secret-value")
|
|
var checked []configkit.Version
|
|
var validationCalls int
|
|
definition := configkit.Definition[configuration]{
|
|
Defaults: func() configuration {
|
|
return configuration{Service: serviceConfig{Defaulted: "from defaults"}}
|
|
},
|
|
Version: &configkit.VersionRule{
|
|
Path: configkit.Keys("version"),
|
|
Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
checked = append(checked, version)
|
|
return nil
|
|
},
|
|
},
|
|
Resolve: func(current *configuration, resolution *configkit.Resolution) error {
|
|
current.Service.Resolved = "resolved once"
|
|
resolution.Record(configkit.Keys("service", "resolved"), configkit.ChangeNormalization, "derive resolved value")
|
|
return nil
|
|
},
|
|
Validate: func(current *configuration, validation *configkit.Validation) {
|
|
validationCalls++
|
|
if current.Service.Version != 9007199254740993 || current.Service.Count != 0 || current.Service.Enabled ||
|
|
current.Service.Nullable != nil || current.Service.Revision != "ordinary-payload" || current.Service.Defaulted != "from defaults" ||
|
|
current.Service.Resolved != "resolved once" || current.Service.Credential.Value() != "lifecycle-secret-value" {
|
|
validation.Error(configkit.Keys("service"), "assembled values did not survive source declaration processing")
|
|
}
|
|
},
|
|
}
|
|
|
|
load := func(header string) *configkit.Result[configuration] {
|
|
t.Helper()
|
|
result, err := configkit.LoadLayered(configkit.Input{
|
|
Source: configkit.Bytes("main", []byte("version: 9007199254740993\n")), Destination: configkit.Keys("service"),
|
|
}, configkit.LayeredOptions[configuration]{
|
|
Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{
|
|
Source: configkit.Bytes("underlay", []byte("version: "+header+"\ncount: 0\nenabled: false\nnullable: null\nrevision: ordinary-payload\ncredential: CONFIGKIT_LIFECYCLE_SECRET\n")),
|
|
Destination: configkit.Keys("service"),
|
|
}}}},
|
|
})
|
|
if err != nil || result == nil {
|
|
var failure *configkit.LoadError
|
|
var diagnostics []configkit.Diagnostic
|
|
if errors.As(err, &failure) {
|
|
diagnostics = failure.Diagnostics()
|
|
}
|
|
t.Fatalf("LoadLayered(%q) = %#v, %v; diagnostics %#v", header, result, err, diagnostics)
|
|
}
|
|
return result
|
|
}
|
|
|
|
first := load("underlay-release-a")
|
|
second := load("underlay-release-b")
|
|
if len(checked) != 4 || checked[0].Text != "9007199254740993" || checked[1].Text != "underlay-release-a" ||
|
|
checked[2].Text != "9007199254740993" || checked[3].Text != "underlay-release-b" {
|
|
t.Fatalf("source declarations checked = %#v", checked)
|
|
}
|
|
if validationCalls != 2 || first.Config.Service != second.Config.Service {
|
|
t.Fatalf("final configs or validation count = %#v, %#v, %d", first.Config, second.Config, validationCalls)
|
|
}
|
|
if differences, err := configkit.Diff(first, second); err != nil || len(differences) != 0 {
|
|
t.Fatalf("different consumed declarations created effective differences: %#v, %v", differences, err)
|
|
}
|
|
var firstEffective, secondEffective bytes.Buffer
|
|
if err := first.WriteEffective(&firstEffective); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := second.WriteEffective(&secondEffective); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if firstEffective.String() != secondEffective.String() || strings.Contains(firstEffective.String(), "underlay-release") {
|
|
t.Fatalf("consumed declarations affected effective rendering:\n%s\n%s", firstEffective.String(), secondEffective.String())
|
|
}
|
|
sourcesA, sourcesB := first.Sources(), second.Sources()
|
|
if len(sourcesA) != 2 || len(sourcesB) != 2 || sourcesA[1].VersionCheck == nil || sourcesB[1].VersionCheck == nil ||
|
|
sourcesA[1].VersionCheck.Version.Text != "underlay-release-a" || sourcesB[1].VersionCheck.Version.Text != "underlay-release-b" {
|
|
t.Fatalf("sanitized evidence did not retain distinct accepted declarations: %#v / %#v", sourcesA, sourcesB)
|
|
}
|
|
versionExplanation, found := first.Explain(configkit.Keys("service", "version"))
|
|
if !found {
|
|
t.Fatal("retained main version missing from effective explanation")
|
|
}
|
|
underlayID := sourcesA[1].ID
|
|
for _, location := range versionExplanation.Origin.Locations {
|
|
if location.SourceID == underlayID {
|
|
t.Fatalf("consumed underlay declaration entered effective origin: %#v", versionExplanation.Origin)
|
|
}
|
|
}
|
|
for _, change := range versionExplanation.History {
|
|
for _, location := range change.Origin.Locations {
|
|
if location.SourceID == underlayID {
|
|
t.Fatalf("consumed underlay declaration entered effective history: %#v", versionExplanation.History)
|
|
}
|
|
}
|
|
}
|
|
}
|