Prepare sequence registration preflight
This commit is contained in:
127
internal/engine/sequence_registration_test.go
Normal file
127
internal/engine/sequence_registration_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompileSequencePolicyNormalizesRegistrations(t *testing.T) {
|
||||
parsed, err := ParsePath("/items/0/a~1b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
registrations []sequenceRegistration
|
||||
want map[string]string
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "identical registrations coalesce",
|
||||
registrations: []sequenceRegistration{
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
},
|
||||
want: map[string]string{"/items": "id"},
|
||||
},
|
||||
{
|
||||
name: "different identity fields conflict",
|
||||
registrations: []sequenceRegistration{
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
{path: Keys("items"), identityField: "name"},
|
||||
},
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "empty identity field is literal",
|
||||
registrations: []sequenceRegistration{{path: Keys("items"), identityField: ""}},
|
||||
want: map[string]string{"/items": ""},
|
||||
},
|
||||
{
|
||||
name: "numeric and escaped tokens remain literal keys",
|
||||
registrations: []sequenceRegistration{{path: parsed, identityField: "id"}},
|
||||
want: map[string]string{"/items/0/a~1b": "id"},
|
||||
},
|
||||
{
|
||||
name: "explicit index is invalid",
|
||||
registrations: []sequenceRegistration{{path: Keys("items").Index(0), identityField: "id"}},
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "unused and root registrations are accepted",
|
||||
registrations: []sequenceRegistration{
|
||||
{path: Path{}, identityField: "id"},
|
||||
{path: Keys("unused"), identityField: "name"},
|
||||
},
|
||||
want: map[string]string{"": "id", "/unused": "name"},
|
||||
},
|
||||
{name: "zero option policy", want: map[string]string{}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
policy, diagnostics := compileSequencePolicy(test.registrations)
|
||||
if (len(diagnostics) != 0) != test.wantError {
|
||||
t.Fatalf("diagnostics = %#v, wantError = %t", diagnostics, test.wantError)
|
||||
}
|
||||
if test.wantError {
|
||||
if diagnostics[0].Code != CodeOptionsInvalid {
|
||||
t.Fatalf("diagnostic code = %q", diagnostics[0].Code)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(policy.identityFields) != len(test.want) {
|
||||
t.Fatalf("compiled fields = %#v, want %#v", policy.identityFields, test.want)
|
||||
}
|
||||
for path, field := range test.want {
|
||||
if got := policy.identityFields[path]; got != field {
|
||||
t.Errorf("identity field at %q = %q, want %q", path, got, field)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSequenceRegistrationPreflightDoesNotReadSources(t *testing.T) {
|
||||
t.Parallel()
|
||||
type config struct {
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
ordinaryReader := strings.NewReader("value: ordinary\n")
|
||||
_, err := Load[config](Reader("ordinary.yml", ordinaryReader), SecretBinding{}, Options[config]{
|
||||
Definition: Definition[config]{sequenceRegistrations: []sequenceRegistration{{path: Keys("items").Index(0), identityField: "id"}}},
|
||||
})
|
||||
requireSequenceRegistrationOptionError(t, err)
|
||||
if ordinaryReader.Len() == 0 {
|
||||
t.Fatal("ordinary reader was consumed before invalid registration was rejected")
|
||||
}
|
||||
|
||||
mainReader := strings.NewReader("value: main\n")
|
||||
underlayReader := strings.NewReader("value: underlay\n")
|
||||
_, err = LoadLayered[config](Input{Source: Reader("main.yml", mainReader)}, SecretBinding{}, LayeredOptions[config]{
|
||||
Definition: Definition[config]{sequenceRegistrations: []sequenceRegistration{{path: Keys("items").Index(0), identityField: "id"}}},
|
||||
Underlays: []Underlay{{Inputs: []Input{{Source: Reader("underlay.yml", underlayReader)}}}},
|
||||
})
|
||||
requireSequenceRegistrationOptionError(t, err)
|
||||
if mainReader.Len() == 0 || underlayReader.Len() == 0 {
|
||||
t.Fatal("layered source was consumed before invalid registration was rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func requireSequenceRegistrationOptionError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatal("expected invalid sequence registration error")
|
||||
}
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("error type = %T, want *LoadError", err)
|
||||
}
|
||||
diagnostics := loadErr.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeOptionsInvalid {
|
||||
t.Fatalf("diagnostics = %#v, want one options diagnostic", diagnostics)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user