Add dynamic sequence identity selectors
This commit is contained in:
@@ -85,6 +85,160 @@ func TestCompileSequencePolicyNormalizesRegistrations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyMatchesExactAndDynamicSelectors(t *testing.T) {
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector(), literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("weather"), literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
for _, path := range []Path{Keys("prompts"), Keys("collections", "weather", "prompts")} {
|
||||
fields, matched := policy.identityFieldsAt(path)
|
||||
if !matched || !equalSequenceIdentityFields(fields, []string{"id"}) {
|
||||
t.Errorf("identityFieldsAt(%q) = %#v, %t", path.String(), fields, matched)
|
||||
}
|
||||
}
|
||||
if _, matched := policy.identityFieldsAt(Keys("collections", "weather", "other")); matched {
|
||||
t.Fatal("selector matched a different destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicSequenceIdentityScopesRemainIndependent(t *testing.T) {
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{{
|
||||
selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector(), literalSequenceSelector("prompts")},
|
||||
identityFields: []string{"id"},
|
||||
}})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
input := parseCompositionDocument(t, "source", "collections:\n weatherreporter:\n prompts: [{id: shared}]\n homewatch:\n prompts: [{id: shared}]\n")
|
||||
if _, diagnostics := composeDocumentsWithPolicy(policy, input); len(diagnostics) != 0 {
|
||||
t.Fatalf("same identity in distinct matched destinations conflicted: %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyMatchesMultipleWildcardLevelsAndScopes(t *testing.T) {
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{
|
||||
literalSequenceSelector("collections"), anyMappingSequenceSelector(),
|
||||
literalSequenceSelector("groups"), anyMappingSequenceSelector(), literalSequenceSelector("prompts"),
|
||||
}, identityFields: []string{"namespace", "id"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
for _, path := range []Path{
|
||||
Keys("collections", "weather", "groups", "one", "prompts"),
|
||||
Keys("collections", "weather", "groups", "two", "prompts"),
|
||||
} {
|
||||
fields, matched := policy.identityFieldsAt(path)
|
||||
if !matched || !equalSequenceIdentityFields(fields, []string{"id", "namespace"}) {
|
||||
t.Errorf("identityFieldsAt(%q) = %#v, %t", path.String(), fields, matched)
|
||||
}
|
||||
}
|
||||
if !policy.registered(Keys("collections", "weather", "groups", "two", "prompts")) {
|
||||
t.Fatal("dynamic registration did not match a separate uniqueness scope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyPreservesLiteralKeysAndRejectsIndexPaths(t *testing.T) {
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector(""), literalSequenceSelector("a/b~c.%"), literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
if _, matched := policy.identityFieldsAt(Keys("", "a/b~c.%", "prompts")); !matched {
|
||||
t.Fatal("selector did not preserve empty and punctuation keys")
|
||||
}
|
||||
if _, matched := policy.identityFieldsAt(Keys("", "a/b~c.%", "prompts").Index(0)); matched {
|
||||
t.Fatal("selector matched a sequence-index destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyCoalescesEquivalentFieldSets(t *testing.T) {
|
||||
selector := sequenceIdentitySelector{literalSequenceSelector("items")}
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: selector, identityFields: []string{"namespace", "id", "id"}},
|
||||
{selector: cloneSequenceIdentitySelector(selector), identityFields: []string{"id", "namespace"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
if leaf := policy.selectorTrie.literals["items"]; leaf == nil || !leaf.terminal {
|
||||
t.Fatal("equivalent selector did not compile to one terminal trie entry")
|
||||
}
|
||||
fields, matched := policy.identityFieldsAt(Keys("items"))
|
||||
if !matched || !equalSequenceIdentityFields(fields, []string{"id", "namespace"}) {
|
||||
t.Fatalf("identity fields = %#v, matched = %t", fields, matched)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyRejectsConflictingOverlapsWithoutInput(t *testing.T) {
|
||||
_, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector(), literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("weather"), literalSequenceSelector("prompts")}, identityFields: []string{"name"}},
|
||||
})
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeOptionsInvalid {
|
||||
t.Fatalf("diagnostics = %#v, want overlap definition error", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyAllowsDisjointSelectorsAndRoot(t *testing.T) {
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"name"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector()}, identityFields: []string{"key"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("static"), literalSequenceSelector("prompts")}, identityFields: []string{"id"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
if fields, matched := policy.identityFieldsAt(Path{}); !matched || !equalSequenceIdentityFields(fields, []string{"id"}) {
|
||||
t.Fatalf("root selector = %#v, %t", fields, matched)
|
||||
}
|
||||
for _, path := range []Path{Keys("items"), Keys("collections", "dynamic"), Keys("collections", "static", "prompts")} {
|
||||
if _, matched := policy.identityFieldsAt(path); !matched {
|
||||
t.Errorf("disjoint selector failed to match %q", path.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicySnapshotsSelectorAndFields(t *testing.T) {
|
||||
selector := sequenceIdentitySelector{literalSequenceSelector("items")}
|
||||
fields := []string{"id", "namespace"}
|
||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{{selector: selector, identityFields: fields}})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
selector[0].key = "changed"
|
||||
fields[0] = "changed"
|
||||
if got, matched := policy.identityFieldsAt(Keys("items")); !matched || !equalSequenceIdentityFields(got, []string{"id", "namespace"}) {
|
||||
t.Fatalf("compiled registration changed after input mutation: %#v, %t", got, matched)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileSequenceIdentityPolicyRejectsInvalidSelectorsAndEmptyFields(t *testing.T) {
|
||||
_, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("configkit")}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{{kind: sequenceSelectorSegmentKind(99)}}, identityFields: []string{"id"}},
|
||||
{selector: sequenceIdentitySelector{literalSequenceSelector("unused")}, identityFields: nil},
|
||||
})
|
||||
if len(diagnostics) != 3 {
|
||||
t.Fatalf("diagnostics = %#v, want all three definition errors", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func literalSequenceSelector(key string) sequenceSelectorSegment {
|
||||
return sequenceSelectorSegment{kind: sequenceSelectorLiteral, key: key}
|
||||
}
|
||||
|
||||
func anyMappingSequenceSelector() sequenceSelectorSegment {
|
||||
return sequenceSelectorSegment{kind: sequenceSelectorAnyMappingKey}
|
||||
}
|
||||
|
||||
func TestSequenceRegistrationPreflightDoesNotReadSources(t *testing.T) {
|
||||
t.Parallel()
|
||||
type config struct {
|
||||
|
||||
Reference in New Issue
Block a user