275 lines
9.4 KiB
Go
275 lines
9.4 KiB
Go
package configkit
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestMetadataHistoricalPaths(t *testing.T) {
|
|
t.Parallel()
|
|
result, err := Load[map[string]any](Bytes("base", []byte("retired: {child: 1}\nshape: {'0': base}\n'a/b~': {'': 2}\n")), Options[map[string]any]{
|
|
Overrides: []Override{
|
|
{Path: Keys("retired"), Value: nil, Flag: "--retired"},
|
|
{Path: Keys("shape"), Value: []any{"replacement"}, Flag: "--shape"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metadata := result.Metadata()
|
|
paths := metadata.HistoricalPaths()
|
|
var got []string
|
|
for _, path := range paths {
|
|
got = append(got, path.String())
|
|
if len(metadata.History(path)) == 0 {
|
|
t.Fatalf("enumerated path %s has no history", path)
|
|
}
|
|
}
|
|
want := []string{"", "/a~1b~0", "/a~1b~0/", "/retired", "/retired/child", "/shape", "/shape/0"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("historical paths = %v, want %v", got, want)
|
|
}
|
|
if _, ok := metadata.Lookup(paths[4]); ok {
|
|
t.Fatal("removed descendant remains effective")
|
|
}
|
|
if _, ok := metadata.Lookup(paths[6]); !ok {
|
|
t.Fatal("enumerated numeric path cannot query the new sequence shape")
|
|
}
|
|
// Returned slices and nested paths must not alias retained metadata.
|
|
paths[1].segments[0].key = "mutated"
|
|
paths[2] = Keys("replacement")
|
|
if again := metadata.HistoricalPaths(); again[1].String() != want[1] || again[2].String() != want[2] {
|
|
t.Fatal("enumeration returned aliased paths")
|
|
}
|
|
if len((Metadata{}).HistoricalPaths()) != 0 {
|
|
t.Fatal("zero metadata contains paths")
|
|
}
|
|
}
|
|
|
|
func TestHistoricalPathsExposeHiddenBaseOwnershipWithoutResolvingBaseSecrets(t *testing.T) {
|
|
t.Setenv("CONFIGKIT_HISTORY_UNUSED_PROD_KEY", "")
|
|
t.Setenv("CONFIGKIT_HISTORY_TEST_KEY", "synthetic-test-key")
|
|
type config struct {
|
|
Credential *Secret `yaml:"credential"`
|
|
Definitions map[string]map[string]string `yaml:"definitions"`
|
|
}
|
|
for _, credential := range []string{"CONFIGKIT_HISTORY_TEST_KEY", "null"} {
|
|
t.Run(credential, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
root := writeImportFile(t, dir, "main.yml", "configkit:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n")
|
|
writeImportFile(t, dir, "first.yml", "definitions: {recap: {prompt: recap}}\n")
|
|
writeImportFile(t, dir, "second.yml", "definitions: {recap: {output: recap.md}}\n")
|
|
writeImportFile(t, dir, "profiles/testing.yml", "definitions: null\ncredential: "+credential+"\n")
|
|
result, err := Load[config](File(root), Options[config]{Imports: true, Profile: "testing"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metadata := result.Metadata()
|
|
found := false
|
|
for _, path := range metadata.HistoricalPaths() {
|
|
if path.String() != "/definitions/recap" {
|
|
continue
|
|
}
|
|
found = true
|
|
owners := map[string]bool{}
|
|
for _, change := range metadata.History(path) {
|
|
if change.Origin.Layer == LayerBase {
|
|
for _, location := range change.Origin.Locations {
|
|
owners[location.SourceID] = true
|
|
}
|
|
}
|
|
}
|
|
if len(owners) != 2 {
|
|
t.Fatalf("hidden declaration owners = %v", owners)
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("hidden base declaration was not discoverable")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMetadataQueriesReturnDetachedRecords(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
path := Keys("service", "workers")
|
|
metadata := Metadata{
|
|
effectivePaths: map[string]Path{path.String(): path},
|
|
historicalPaths: map[string][]Path{path.String(): {path}},
|
|
fields: map[string]FieldInfo{
|
|
path.String(): {
|
|
Explicit: true,
|
|
Origin: Origin{
|
|
Layer: LayerBase,
|
|
Locations: []Location{{SourceID: "source-1", Line: 2, Column: 3}},
|
|
},
|
|
},
|
|
},
|
|
history: map[string][]Change{
|
|
path.String(): {{
|
|
Path: path,
|
|
Origin: Origin{Layer: LayerResolution, Locations: []Location{{SourceID: "source-1"}}},
|
|
Previous: []Origin{{Layer: LayerDefault, Locations: []Location{{SourceID: "default"}}}},
|
|
Reasons: []string{"bounded worker count"},
|
|
Dependencies: []Path{Keys("limits")},
|
|
}},
|
|
},
|
|
}
|
|
|
|
info, ok := metadata.Lookup(path)
|
|
if !ok || !info.Explicit || !metadata.IsExplicit(path) {
|
|
t.Fatalf("Lookup() = %#v, %v", info, ok)
|
|
}
|
|
info.Origin.Locations[0].SourceID = "mutated"
|
|
again, _ := metadata.Lookup(path)
|
|
if again.Origin.Locations[0].SourceID != "source-1" {
|
|
t.Fatal("Lookup returned aliased locations")
|
|
}
|
|
|
|
history := metadata.History(path)
|
|
history[0].Reasons[0] = "mutated"
|
|
history[0].Previous[0].Locations[0].SourceID = "mutated"
|
|
history[0].Dependencies[0].segments[0].key = "mutated"
|
|
againHistory := metadata.History(path)
|
|
if againHistory[0].Reasons[0] != "bounded worker count" ||
|
|
againHistory[0].Previous[0].Locations[0].SourceID != "default" ||
|
|
againHistory[0].Dependencies[0].String() != "/limits" {
|
|
t.Fatal("History returned aliased nested records")
|
|
}
|
|
|
|
missing := Keys("missing")
|
|
if _, ok := metadata.Lookup(missing); ok || metadata.IsExplicit(missing) || metadata.History(missing) != nil {
|
|
t.Fatal("unknown path produced metadata")
|
|
}
|
|
if _, ok := metadata.Lookup(Path{}.Index(-1)); ok {
|
|
t.Fatal("invalid path produced metadata")
|
|
}
|
|
}
|
|
|
|
func TestResolutionInputKeepsDetachedEntryMetadata(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type configuration struct {
|
|
Default string `yaml:"default"`
|
|
Items []string `yaml:"items"`
|
|
Values map[string]string `yaml:"values"`
|
|
Nullable *string `yaml:"nullable"`
|
|
}
|
|
schema := mustSchemaFor[configuration](t)
|
|
document := parseDocumentForDecode(t, "items: [first]\nvalues: {'0': key, 'a/b~': escaped}\nnullable: null\n")
|
|
decoded, issues := decodeConfiguration(schema, reflect.ValueOf(configuration{Default: "retained"}), document)
|
|
if len(issues) != 0 {
|
|
t.Fatalf("decode issues = %#v", issues)
|
|
}
|
|
metadata := metadataFromDecoded(decoded)
|
|
input := newResolutionInput(metadata, decoded.final, []SourceInfo{{
|
|
ID: "source-1", Label: "decode.yml", Layer: LayerBase,
|
|
ImportedFrom: &Location{SourceID: "parent", Line: 1, Column: 1},
|
|
}})
|
|
|
|
parsedItem, err := ParsePath("/items/0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parsedKey, err := ParsePath("/values/0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []Path{Path{}, Keys("default"), Keys("nullable"), parsedItem, parsedKey, Keys("values", "a/b~")} {
|
|
if _, ok := input.lookup(path); !ok {
|
|
t.Fatalf("entry path %s is missing", path)
|
|
}
|
|
}
|
|
defaultInfo, _ := input.lookup(Keys("default"))
|
|
nullInfo, _ := input.lookup(Keys("nullable"))
|
|
if defaultInfo.Explicit || !nullInfo.Explicit || !nullInfo.Null {
|
|
t.Fatalf("default = %#v, nullable = %#v", defaultInfo, nullInfo)
|
|
}
|
|
for _, path := range []Path{
|
|
Keys("default", "child"),
|
|
Keys("items", "0"),
|
|
Keys("values").Index(0),
|
|
Keys("items").Index(1),
|
|
} {
|
|
if _, ok := input.lookup(path); ok {
|
|
t.Fatalf("entry lookup accepted %s", path)
|
|
}
|
|
}
|
|
|
|
item, _ := input.lookup(parsedItem)
|
|
item.Origin.Locations[0].SourceID = "mutated"
|
|
source, ok := input.source("source-1")
|
|
if !ok {
|
|
t.Fatal("source is missing")
|
|
}
|
|
source.ImportedFrom.SourceID = "mutated"
|
|
|
|
configurationAtEntry := decoded.value.Interface().(configuration)
|
|
resolved := runResolution(&configurationAtEntry, func(value *configuration, resolution *Resolution) error {
|
|
value.Items[0] = "normalized"
|
|
resolution.Record(Keys("items"), ChangeNormalization, "normalized item")
|
|
return nil
|
|
}, schema, decoded, document, input)
|
|
if len(resolved.diagnostics) != 0 {
|
|
t.Fatalf("resolution diagnostics = %#v", resolved.diagnostics)
|
|
}
|
|
applyResolutionMetadata(&metadata, resolved)
|
|
metadata.effectivePaths = effectivePathIndex(resolved.final)
|
|
|
|
again, ok := input.lookup(parsedItem)
|
|
if !ok || again.Origin.Locations[0].SourceID != "source-1" || again.Origin.Operation != "" {
|
|
t.Fatalf("entry metadata changed after resolution = %#v, %v", again, ok)
|
|
}
|
|
againSource, ok := input.source("source-1")
|
|
if !ok || againSource.ImportedFrom == nil || againSource.ImportedFrom.SourceID != "parent" {
|
|
t.Fatalf("entry source changed = %#v, %v", againSource, ok)
|
|
}
|
|
if _, ok := input.source(""); ok {
|
|
t.Fatal("empty source identity resolved")
|
|
}
|
|
}
|
|
|
|
func TestQueriesRespectActualHistoricalAndEffectiveContainers(t *testing.T) {
|
|
result, err := Load[map[string]any](Bytes("base", []byte("items: [1]\nm: {'0': 2}\nx: 3\nretired: {child: 4}\n")), Options[map[string]any]{Overrides: []Override{{Path: Keys("retired"), Value: "removed", Flag: "--retired"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []Path{Keys("items", "0"), Keys("m").Index(0), Keys("x", "missing"), Keys("items").Index(8)} {
|
|
if _, ok := result.Metadata().Lookup(path); ok {
|
|
t.Fatalf("invalid lookup %s", path)
|
|
}
|
|
if result.Metadata().History(path) != nil {
|
|
t.Fatalf("invalid history %s", path)
|
|
}
|
|
if _, ok := result.Explain(path); ok {
|
|
t.Fatalf("invalid explanation %s", path)
|
|
}
|
|
var out bytes.Buffer
|
|
if err := result.WriteExplanation(&out, path); err == nil || out.Len() != 0 {
|
|
t.Fatalf("invalid path wrote output: %s", path)
|
|
}
|
|
}
|
|
for _, pointer := range []string{"/items/0", "/m/0"} {
|
|
path, _ := ParsePath(pointer)
|
|
if _, ok := result.Metadata().Lookup(path); !ok {
|
|
t.Fatal("parsed path lost lookup")
|
|
}
|
|
if _, ok := result.Explain(path); !ok {
|
|
t.Fatal("parsed path lost explanation")
|
|
}
|
|
}
|
|
retired := Keys("retired", "child")
|
|
if _, ok := result.Metadata().Lookup(retired); ok {
|
|
t.Fatal("removed path remains effective")
|
|
}
|
|
if len(result.Metadata().History(retired)) == 0 {
|
|
t.Fatal("removed history missing")
|
|
}
|
|
x, ok := result.Explain(retired)
|
|
if !ok || x.Effective.Present || len(x.History) == 0 {
|
|
t.Fatal("removed explanation missing")
|
|
}
|
|
}
|