Expose sequence identity registrations through the facade
This commit is contained in:
@@ -7,54 +7,44 @@ import (
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
func TestCompileSequencePolicyNormalizesRegistrations(t *testing.T) {
|
||||
parsed, err := ParsePath("/items/0/a~1b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyNormalizesRegistrations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
registrations []sequenceRegistration
|
||||
registrations []sequenceIdentityRegistration
|
||||
want map[string]string
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "identical registrations coalesce",
|
||||
registrations: []sequenceRegistration{
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
registrations: []sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}},
|
||||
},
|
||||
want: map[string]string{"/items": "id"},
|
||||
},
|
||||
{
|
||||
name: "different identity fields conflict",
|
||||
registrations: []sequenceRegistration{
|
||||
{path: Keys("items"), identityField: "id"},
|
||||
{path: Keys("items"), identityField: "name"},
|
||||
registrations: []sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"name"}},
|
||||
},
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "empty identity field is literal",
|
||||
registrations: []sequenceRegistration{{path: Keys("items"), identityField: ""}},
|
||||
registrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{""}}},
|
||||
want: map[string]string{"/items": ""},
|
||||
},
|
||||
{
|
||||
name: "numeric and escaped tokens remain literal keys",
|
||||
registrations: []sequenceRegistration{{path: parsed, identityField: "id"}},
|
||||
registrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items"), literalSequenceSelector("0"), literalSequenceSelector("a/b")}, identityFields: []string{"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"},
|
||||
registrations: []sequenceIdentityRegistration{
|
||||
{selector: nil, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("unused")}, identityFields: []string{"name"}},
|
||||
},
|
||||
want: map[string]string{"": "id", "/unused": "name"},
|
||||
},
|
||||
@@ -63,7 +53,7 @@ func TestCompileSequencePolicyNormalizesRegistrations(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
policy, diagnostics := compileSequencePolicy(test.registrations)
|
||||
policy, diagnostics := compileSequenceIdentityPolicy(test.registrations)
|
||||
if (len(diagnostics) != 0) != test.wantError {
|
||||
t.Fatalf("diagnostics = %#v, wantError = %t", diagnostics, test.wantError)
|
||||
}
|
||||
@@ -73,12 +63,14 @@ func TestCompileSequencePolicyNormalizesRegistrations(t *testing.T) {
|
||||
}
|
||||
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)
|
||||
parsed, err := ParsePath(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotFields, ok := policy.identityFieldsAt(parsed)
|
||||
if !ok || len(gotFields) != 1 || gotFields[0] != field {
|
||||
t.Errorf("identity fields at %q = %#v, want [%q]", path, gotFields, field)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -247,7 +239,7 @@ func TestSequenceRegistrationPreflightDoesNotReadSources(t *testing.T) {
|
||||
|
||||
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"}}},
|
||||
Definition: DefinitionWithSequenceIdentityRegistrations(Definition[config]{}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items").Index(0)), IdentityFields: []string{"id"}}}),
|
||||
})
|
||||
requireSequenceRegistrationOptionError(t, err)
|
||||
if ordinaryReader.Len() == 0 {
|
||||
@@ -257,7 +249,7 @@ func TestSequenceRegistrationPreflightDoesNotReadSources(t *testing.T) {
|
||||
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"}}},
|
||||
Definition: DefinitionWithSequenceIdentityRegistrations(Definition[config]{}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items").Index(0)), IdentityFields: []string{"id"}}}),
|
||||
Underlays: []Underlay{{Inputs: []Input{{Source: Reader("underlay.yml", underlayReader)}}}},
|
||||
})
|
||||
requireSequenceRegistrationOptionError(t, err)
|
||||
@@ -266,6 +258,8 @@ func TestSequenceRegistrationPreflightDoesNotReadSources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func pathPointer(path Path) *Path { return &path }
|
||||
|
||||
func requireSequenceRegistrationOptionError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
@@ -289,7 +283,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
definition := Definition[config]{sequenceRegistrations: []sequenceRegistration{{path: Keys("items"), identityField: "id"}}}
|
||||
definition := Definition[config]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}}
|
||||
registered := Options[config]{Definition: definition}
|
||||
loadCode := func(t *testing.T, err error) Code {
|
||||
t.Helper()
|
||||
@@ -327,7 +321,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
{source: Bytes("first", []byte("items: [{id: lower}]\n"))},
|
||||
{source: Bytes("second", []byte("items: [{id: other}]\n"))},
|
||||
}}},
|
||||
definition: definition, sequencePolicy: mustCompileSequencePolicy(t, definition.sequenceRegistrations),
|
||||
definition: definition, sequencePolicy: mustCompileSequenceIdentityPolicy(t, definition.sequenceIdentityRegistrations),
|
||||
}
|
||||
result, err := testGroupedLoad(request)
|
||||
if err != nil || len(result.Config.Items) != 3 || result.Config.Items[0].ID != "lower" || result.Config.Items[1].ID != "other" || result.Config.Items[2].ID != "shared" {
|
||||
@@ -352,7 +346,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
request := loadRequest[config]{
|
||||
main: loadInput{source: Bytes("main", []byte("items: []\n"))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("items: [{id: lower}]\n"))}}}},
|
||||
definition: definition, sequencePolicy: mustCompileSequencePolicy(t, definition.sequenceRegistrations),
|
||||
definition: definition, sequencePolicy: mustCompileSequenceIdentityPolicy(t, definition.sequenceIdentityRegistrations),
|
||||
}
|
||||
result, err = testGroupedLoad(request)
|
||||
if err != nil || len(result.Config.Items) != 1 || result.Config.Items[0].ID != "lower" {
|
||||
@@ -378,7 +372,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
request := loadRequest[config]{
|
||||
main: loadInput{source: Bytes("main", []byte("items: [{id: main}]\n"))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("items: [{id: same}, {id: same}]\n"))}}}},
|
||||
definition: definition, sequencePolicy: mustCompileSequencePolicy(t, definition.sequenceRegistrations),
|
||||
definition: definition, sequencePolicy: mustCompileSequenceIdentityPolicy(t, definition.sequenceIdentityRegistrations),
|
||||
}
|
||||
if got := loadCode(t, func() error { _, err := testGroupedLoad(request); return err }()); got != CodeComposeConflict {
|
||||
t.Fatalf("hidden lower-tier error = %s", got)
|
||||
@@ -447,9 +441,9 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func mustCompileSequencePolicy(t *testing.T, registrations []sequenceRegistration) sequencePolicy {
|
||||
func mustCompileSequenceIdentityPolicy(t *testing.T, registrations []sequenceIdentityRegistration) sequencePolicy {
|
||||
t.Helper()
|
||||
policy, diagnostics := compileSequencePolicy(registrations)
|
||||
policy, diagnostics := compileSequenceIdentityPolicy(registrations)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user