Reset inherited YAML identity on actual replacement
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user