Validate final registered sequence identities
This commit is contained in:
201
facade_final_sequence_identity_test.go
Normal file
201
facade_final_sequence_identity_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package configkit_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit"
|
||||
)
|
||||
|
||||
func TestFinalSequenceIdentityValidatesLateValues(t *testing.T) {
|
||||
identity := configkit.SequenceIdentityAt(configkit.Keys("items"), "id")
|
||||
for _, source := range []string{"defaults", "CLI", "resolution"} {
|
||||
for _, shape := range []string{"duplicate", "missing", "non-scalar"} {
|
||||
t.Run(source+"/"+shape, func(t *testing.T) {
|
||||
items := finalIdentityRecords(shape)
|
||||
definition := configkit.Definition[map[string]any]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{identity},
|
||||
Defaults: func() map[string]any {
|
||||
if source == "defaults" {
|
||||
return map[string]any{"items": items}
|
||||
}
|
||||
return map[string]any{}
|
||||
},
|
||||
}
|
||||
options := configkit.Options[map[string]any]{Definition: definition}
|
||||
switch source {
|
||||
case "CLI":
|
||||
options.Overrides = []configkit.Override{{Path: configkit.Keys("items"), Value: items, Flag: "--items"}}
|
||||
case "resolution":
|
||||
options.Definition.Resolve = func(value *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*value)["items"] = items
|
||||
resolution.Record(configkit.Keys("items"), configkit.ChangeNormalization, "create effective records")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
result, err := configkit.Load(configkit.NoSource(), options)
|
||||
if result != nil {
|
||||
t.Fatalf("invalid final identities returned a result: %#v", result.Config)
|
||||
}
|
||||
loadErr := requireFacadeLoadError(t, err)
|
||||
if len(loadErr.Diagnostics()) != 1 {
|
||||
t.Fatalf("diagnostics = %#v", loadErr.Diagnostics())
|
||||
}
|
||||
diagnostic := loadErr.Diagnostics()[0]
|
||||
wantCode, wantPath := configkit.CodeSequenceInvalid, "/items/0/id"
|
||||
if shape == "duplicate" {
|
||||
wantCode, wantPath = configkit.CodeSequenceConflict, "/items"
|
||||
}
|
||||
if diagnostic.Code != wantCode || diagnostic.Path.String() != wantPath {
|
||||
t.Fatalf("diagnostic = %#v, want %s at %s", diagnostic, wantCode, wantPath)
|
||||
}
|
||||
if source == "CLI" && (diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1") {
|
||||
t.Fatalf("CLI identity diagnostic lost its source: %#v", diagnostic)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalSequenceIdentityMatchesLateDynamicNamespaces(t *testing.T) {
|
||||
selector := configkit.SequenceSelector{
|
||||
configkit.SequenceLiteralKey("collections"),
|
||||
configkit.SequenceAnyMappingKey(),
|
||||
configkit.SequenceLiteralKey("items"),
|
||||
}
|
||||
for _, source := range []string{"defaults", "CLI", "resolution"} {
|
||||
t.Run(source, func(t *testing.T) {
|
||||
name := map[string]string{"defaults": "team", "CLI": "cloud", "resolution": "orbit"}[source]
|
||||
path := configkit.Keys("collections", name, "items")
|
||||
items := finalIdentityRecords("duplicate")
|
||||
definition := configkit.Definition[map[string]any]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{
|
||||
configkit.SequenceIdentityMatching(selector, "id"),
|
||||
},
|
||||
Defaults: func() map[string]any {
|
||||
if source == "defaults" {
|
||||
return map[string]any{"collections": map[string]any{name: map[string]any{"items": items}}}
|
||||
}
|
||||
return map[string]any{}
|
||||
},
|
||||
}
|
||||
options := configkit.Options[map[string]any]{Definition: definition}
|
||||
switch source {
|
||||
case "CLI":
|
||||
options.Overrides = []configkit.Override{{Path: path, Value: items, Flag: "--items"}}
|
||||
case "resolution":
|
||||
options.Definition.Resolve = func(value *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*value)["collections"] = map[string]any{name: map[string]any{"items": items}}
|
||||
resolution.Record(configkit.Keys("collections"), configkit.ChangeNormalization, "create namespaced records")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
result, err := configkit.Load(configkit.NoSource(), options)
|
||||
if result != nil {
|
||||
t.Fatalf("invalid dynamic identity returned a result: %#v", result.Config)
|
||||
}
|
||||
loadErr := requireFacadeLoadError(t, err)
|
||||
if diagnostics := loadErr.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeSequenceConflict || diagnostics[0].Path.String() != path.String() {
|
||||
t.Fatalf("dynamic identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalSequenceIdentityAppliesToLayeredLoads(t *testing.T) {
|
||||
definition := configkit.Definition[map[string]any]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
|
||||
Defaults: func() map[string]any {
|
||||
return map[string]any{"items": finalIdentityRecords("duplicate")}
|
||||
},
|
||||
}
|
||||
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[map[string]any]{Definition: definition})
|
||||
if result != nil {
|
||||
t.Fatalf("invalid layered final identities returned a result: %#v", result.Config)
|
||||
}
|
||||
loadErr := requireFacadeLoadError(t, err)
|
||||
if diagnostics := loadErr.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeSequenceConflict {
|
||||
t.Fatalf("layered identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalSequenceIdentityUsesTypedNumbersAndIgnoresUnmatchedShapes(t *testing.T) {
|
||||
definition := configkit.Definition[map[string]any]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{
|
||||
configkit.SequenceIdentityAt(configkit.Keys("items"), "id"),
|
||||
configkit.SequenceIdentityAt(configkit.Keys("unmatched"), "id"),
|
||||
},
|
||||
}
|
||||
_, err := configkit.Load(configkit.NoSource(), configkit.Options[map[string]any]{
|
||||
Definition: configkit.Definition[map[string]any]{
|
||||
SequenceIdentities: definition.SequenceIdentities,
|
||||
Defaults: func() map[string]any {
|
||||
return map[string]any{"items": []any{
|
||||
map[string]any{"id": int64(1)}, map[string]any{"id": float64(1)},
|
||||
}}
|
||||
},
|
||||
},
|
||||
})
|
||||
loadErr := requireFacadeLoadError(t, err)
|
||||
if diagnostics := loadErr.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeSequenceConflict {
|
||||
t.Fatalf("typed numeric identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
|
||||
valid, err := configkit.Load(configkit.Mapping("valid", map[string]any{
|
||||
"items": []any{
|
||||
map[string]any{"id": nil}, map[string]any{"id": true},
|
||||
map[string]any{"id": int64(1)}, map[string]any{"id": "one"},
|
||||
},
|
||||
"other": []any{map[string]any{"id": "same"}, map[string]any{"id": "same"}},
|
||||
}), configkit.Options[map[string]any]{Definition: definition})
|
||||
if err != nil || valid == nil {
|
||||
t.Fatalf("valid scalar identities and unmatched duplicate sequence = %#v, %v", valid, err)
|
||||
}
|
||||
if _, err := configkit.Load(configkit.Mapping("other-shapes", map[string]any{"items": nil}), configkit.Options[map[string]any]{Definition: definition}); err != nil {
|
||||
t.Fatalf("null at a registered path was validated as an identity sequence: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalSequenceIdentityDiagnosticDoesNotRevealResolvedSecrets(t *testing.T) {
|
||||
const canary = "FINAL-IDENTITY-CREDENTIAL-CANARY"
|
||||
const secretName = "CONFIGKIT_FINAL_IDENTITY_SECRET"
|
||||
t.Setenv(secretName, canary)
|
||||
type item struct {
|
||||
ID string `yaml:"id"`
|
||||
Token configkit.Secret `yaml:"token"`
|
||||
}
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
definition := configkit.Definition[config]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
|
||||
Defaults: func() config {
|
||||
return config{Items: []item{
|
||||
{ID: "duplicate", Token: configkit.SecretEnv(secretName)},
|
||||
{ID: "duplicate", Token: configkit.SecretEnv(secretName)},
|
||||
}}
|
||||
},
|
||||
}
|
||||
result, err := configkit.Load(configkit.NoSource(), configkit.Options[config]{Definition: definition})
|
||||
if result != nil {
|
||||
t.Fatalf("duplicate identity returned a result: %#v", result.Config)
|
||||
}
|
||||
if err == nil || strings.Contains(err.Error(), canary) {
|
||||
t.Fatalf("identity diagnostic leaked a resolved secret: %v", err)
|
||||
}
|
||||
loadErr := requireFacadeLoadError(t, err)
|
||||
if diagnostics := loadErr.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeSequenceConflict {
|
||||
t.Fatalf("secret-bearing identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func finalIdentityRecords(shape string) []any {
|
||||
switch shape {
|
||||
case "duplicate":
|
||||
return []any{map[string]any{"id": "same"}, map[string]any{"id": "same"}}
|
||||
case "missing":
|
||||
return []any{map[string]any{"other": "value"}}
|
||||
default:
|
||||
return []any{map[string]any{"id": map[string]any{"nested": "value"}}}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user