Reset inherited YAML identity on actual replacement

This commit is contained in:
2026-10-24 00:07:22 +00:00
parent f7624ae92a
commit 8ecfedc91b
4 changed files with 120 additions and 14 deletions

View File

@@ -1,11 +1,99 @@
package engine
import (
"reflect"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestInheritanceReplacementResetsOnlyReplacedYamlIdentity(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name, key, value, applicationKey string
mode sequenceMode
wantFields []string
conflict bool
}{
{name: "changed key", key: "name", value: "[{name: child}]", mode: sequenceModeReplace, wantFields: []string{"name"}},
{name: "unkeyed replacement", value: "[{name: child}]", mode: sequenceModeReplace},
{name: "non-sequence replacement", value: "42", mode: sequenceModeReplace},
{name: "agreeing application", key: "name", value: "[{name: child}]", applicationKey: "name", mode: sequenceModeReplace, wantFields: []string{"name"}},
{name: "conflicting application", key: "name", value: "[{name: child}]", applicationKey: "id", mode: sequenceModeReplace, conflict: true},
{name: "application still validates replacement", value: "[{name: child}]", applicationKey: "id", mode: sequenceModeReplace, conflict: true},
{name: "mode-only key change", key: "name", mode: sequenceModeReplace, conflict: true},
{name: "mode-only keeps identity", mode: sequenceModeReplace, wantFields: []string{"id"}},
{name: "append key change", key: "name", value: "[{name: child}]", mode: sequenceModeAppend, conflict: true},
{name: "upsert key change", key: "name", value: "[{name: child}]", mode: sequenceModeUpsert, conflict: true},
} {
t.Run(test.name, func(t *testing.T) {
input := "base: {items: [{id: parent}]}\nchild:\n extends: base\n"
if test.value != "" {
input += " items: " + test.value + "\n"
}
root := parseItemPolicyDocument(t, "replacement", input)
policies := collectionPolicyScope{}
policies.set(Keys("base", "items"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
higher := collectionPolicy{explicitMode: test.mode, explicitModePresent: true}
if test.key != "" {
higher.identityFields, higher.identityPresent = []string{test.key}, true
}
policies.set(Keys("child", "items"), higher)
application := sequencePolicy{}
if test.applicationKey != "" {
application = mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{
selector: sequenceIdentitySelector{literalSequenceSelector("child"), literalSequenceSelector("items")}, identityFields: []string{test.applicationKey},
}})
}
expanded, diagnostics := expandInheritanceWithCollectionRules(root, nil, policies, application, defaultProjectionLimits())
if test.conflict {
wantCode := CodeSequenceConflict
if test.applicationKey != "" && test.key == "" {
wantCode = CodeSequenceInvalid
}
if len(diagnostics) == 0 || diagnostics[0].Code != wantCode || diagnostics[0].Path.String() != "/child/items" && diagnostics[0].Path.String() != "/child/items/0/id" {
t.Fatalf("missing identity/constraint failure: %#v", diagnostics)
}
return
}
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
policy, found := expanded.collectionPolicies.at(Keys("child", "items"))
if !found || !reflect.DeepEqual(policy.identityFields, test.wantFields) || policy.identityPresent != (test.wantFields != nil) {
t.Fatalf("effective YAML identity = %#v, want %v", policy, test.wantFields)
}
if test.value != "" {
actual, _ := documentAtPath(expanded.root, Keys("child", "items"))
want, _ := documentAtPath(root, Keys("child", "items"))
if !configurationValuesEqual(actual, want) {
t.Fatal("explicit replacement retained parent payload")
}
}
})
}
}
func TestLoadInheritanceReplacementUsesNewYamlKey(t *testing.T) {
t.Parallel()
loaded, err := testLoad[map[string]any](Bytes("replacement", []byte(`configkit:
sequences:
- {destination: [base, items], key: id}
- {destination: [child, items], key: name, mode: replace}
base: {items: [{id: parent}]}
child:
extends: base
items: [{name: child}]
`)))
if err != nil {
t.Fatal(err)
}
value, found := loaded.Explain(Keys("child", "items"))
if !found || value.Effective.YAML != "- name: child\n" {
t.Fatalf("replacement payload = %#v", value)
}
}
func TestInheritanceCombinesIdentifiedSequencesAtDestination(t *testing.T) {
root := parseItemPolicyDocument(t, "inheritance-values", `base:
items: