Preserve nested collection policy ownership
This commit is contained in:
@@ -2,6 +2,7 @@ package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -194,6 +195,161 @@ func TestNestedItemPolicyProjectionUsesStableOutputIndexes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverlaySequenceTransfersIncomingItemPolicyOwnership(t *testing.T) {
|
||||
path := Keys("records")
|
||||
registered := mustCompileSequencePolicy(t, []sequenceRegistration{{path: path, identityField: "id"}})
|
||||
makePolicies := func(childKey string) collectionPolicyScope {
|
||||
local := collectionPolicyScope{}
|
||||
local.set(Keys("steps"), collectionPolicy{
|
||||
identityFields: []string{childKey}, identityPresent: true,
|
||||
explicitMode: sequenceModeAppend, explicitModePresent: true,
|
||||
})
|
||||
record := newCollectionRecordPolicy()
|
||||
record.scope = local
|
||||
outer := collectionPolicy{identityFields: []string{"id"}, identityPresent: true}
|
||||
outer.setRecordPolicy(record)
|
||||
scope := collectionPolicyScope{}
|
||||
scope.set(path, outer)
|
||||
return scope
|
||||
}
|
||||
checkPolicy := func(t *testing.T, scope collectionPolicyScope, index int, key string) {
|
||||
t.Helper()
|
||||
outer, ok := scope.at(path)
|
||||
if !ok {
|
||||
t.Fatal("outer collection policy missing")
|
||||
}
|
||||
record, ok := outer.recordPolicyAt(index)
|
||||
if !ok {
|
||||
t.Fatalf("record policy %d missing", index)
|
||||
}
|
||||
local, ok := record.scope.at(Keys("steps"))
|
||||
if !ok || !local.identityPresent || !reflect.DeepEqual(local.identityFields, []string{key}) || !local.explicitModePresent || local.explicitMode != sequenceModeAppend {
|
||||
t.Fatalf("record %d nested policy = %#v, %v; want key %q and append", index, local, ok, key)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
lower string
|
||||
higher string
|
||||
declarations []sequenceDeclarationSpec
|
||||
wantRecords int
|
||||
}{
|
||||
{
|
||||
name: "upserting the same outer identity replaces its local policy",
|
||||
lower: "records: [{id: same, steps: [{id: old}]}]\n",
|
||||
higher: "records: [{id: same, steps: [{slug: new}]}]\n",
|
||||
wantRecords: 1,
|
||||
},
|
||||
{
|
||||
name: "retained and appended outer records keep independent policies",
|
||||
lower: "records: [{id: lower, steps: [{id: old}]}]\n",
|
||||
higher: "records: [{id: higher, steps: [{slug: new}]}]\n",
|
||||
wantRecords: 2,
|
||||
},
|
||||
{
|
||||
name: "whole sequence replacement transfers new local policy",
|
||||
lower: "records: [{id: same, steps: [{id: old}]}]\n",
|
||||
higher: "records: [{id: same, steps: [{slug: new}]}]\n",
|
||||
declarations: []sequenceDeclarationSpec{{
|
||||
destination: path, mode: sequenceModeReplace, modePresent: true,
|
||||
}},
|
||||
wantRecords: 1,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
lowerRoot := parseItemPolicyDocument(t, "lower", test.lower)
|
||||
incomingRoot := parseItemPolicyDocument(t, "higher", test.higher)
|
||||
assembly := newAssemblyAccumulator(&composedDocument{
|
||||
root: lowerRoot, collectionPolicies: makePolicies("id"), origins: make(map[string]Origin),
|
||||
})
|
||||
if diagnostics := assembly.applyOverlayWithCollectionPolicy(
|
||||
incomingRoot, nil, makePolicies("slug"), nil, LayerBase, registered, test.declarations,
|
||||
); len(diagnostics) != 0 {
|
||||
t.Fatalf("overlay diagnostics = %#v", diagnostics)
|
||||
}
|
||||
outer, ok := assembly.current.collectionPolicies.at(path)
|
||||
if !ok || len(outer.recordOrder) != test.wantRecords {
|
||||
t.Fatalf("outer record policies = %#v, %v", outer, ok)
|
||||
}
|
||||
if test.wantRecords == 2 {
|
||||
checkPolicy(t, assembly.current.collectionPolicies, 0, "id")
|
||||
checkPolicy(t, assembly.current.collectionPolicies, 1, "slug")
|
||||
} else {
|
||||
checkPolicy(t, assembly.current.collectionPolicies, 0, "slug")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAncestorReplacementRequiresIdentityWhenRetainedModeReturns(t *testing.T) {
|
||||
path := Keys("catalog", "prompts")
|
||||
newAssembly := func(t *testing.T) *assemblyAccumulator {
|
||||
t.Helper()
|
||||
policy := collectionPolicy{
|
||||
identityFields: []string{"id"}, identityPresent: true,
|
||||
explicitMode: sequenceModeAppend, explicitModePresent: true,
|
||||
}
|
||||
scope := collectionPolicyScope{}
|
||||
scope.set(path, policy)
|
||||
return newAssemblyAccumulator(&composedDocument{
|
||||
root: parseItemPolicyDocument(t, "base", "catalog:\n prompts: [{id: old}]\n"),
|
||||
collectionPolicies: scope, origins: make(map[string]Origin),
|
||||
})
|
||||
}
|
||||
reintroduceAncestor := func(t *testing.T, assembly *assemblyAccumulator) {
|
||||
t.Helper()
|
||||
if diagnostics := assembly.applyOverlayWithCollectionPolicy(
|
||||
parseItemPolicyDocument(t, "remove", "catalog: null\n"), nil, collectionPolicyScope{}, nil,
|
||||
LayerBase, sequencePolicy{}, nil,
|
||||
); len(diagnostics) != 0 {
|
||||
t.Fatalf("ancestor removal diagnostics = %#v", diagnostics)
|
||||
}
|
||||
if diagnostics := assembly.applyOverlayWithCollectionPolicy(
|
||||
parseItemPolicyDocument(t, "reintroduction", "catalog:\n prompts: [{id: restored, name: restored}]\n"), nil,
|
||||
collectionPolicyScope{}, nil, LayerBase, sequencePolicy{}, nil,
|
||||
); len(diagnostics) != 0 {
|
||||
t.Fatalf("ancestor reintroduction diagnostics = %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("retained append mode requires a renewed identity", func(t *testing.T) {
|
||||
assembly := newAssembly(t)
|
||||
reintroduceAncestor(t, assembly)
|
||||
policy, ok := assembly.current.collectionPolicies.at(path)
|
||||
if !ok || policy.identityPresent || !policy.explicitModePresent || policy.explicitMode != sequenceModeAppend {
|
||||
t.Fatalf("reintroduced policy = %#v, %v", policy, ok)
|
||||
}
|
||||
diagnostics := assembly.applyOverlayWithCollectionPolicy(
|
||||
parseItemPolicyDocument(t, "next", "catalog:\n prompts: [{value: next}]\n"), nil,
|
||||
collectionPolicyScope{}, nil, LayerBase, sequencePolicy{}, nil,
|
||||
)
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != CodeSequenceInvalid {
|
||||
t.Fatalf("append without renewed identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a renewed key restores the retained append mode", func(t *testing.T) {
|
||||
assembly := newAssembly(t)
|
||||
reintroduceAncestor(t, assembly)
|
||||
declaration := sequenceDeclarationSpec{
|
||||
destination: path, identityFields: []string{"name"}, identityPresent: true,
|
||||
}
|
||||
diagnostics := assembly.applyOverlayWithCollectionPolicy(
|
||||
parseItemPolicyDocument(t, "keyed", "catalog:\n prompts: [{name: next}]\n"), nil,
|
||||
collectionPolicyScope{}, nil, LayerBase, sequencePolicy{}, []sequenceDeclarationSpec{declaration},
|
||||
)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("renewed identity diagnostics = %#v", diagnostics)
|
||||
}
|
||||
policy, ok := assembly.current.collectionPolicies.at(path)
|
||||
if !ok || !policy.identityPresent || !reflect.DeepEqual(policy.identityFields, []string{"name"}) || !policy.explicitModePresent || policy.explicitMode != sequenceModeAppend {
|
||||
t.Fatalf("renewed collection policy = %#v, %v", policy, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestItemSourceVersionProcessingConsumesDeclarationsBeforeComposition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user