Retire obsolete collection identity before replacement agreement
This commit is contained in:
111
internal/engine/collection_retirement_test.go
Normal file
111
internal/engine/collection_retirement_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSourceReplacementRetiresIdentityForEveryValueKind(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, value := range []string{"null", "42", "{}", "[{name: new}]"} {
|
||||
t.Run(value, func(t *testing.T) {
|
||||
lower := "configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"
|
||||
main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\nitems: " + value + "\n"
|
||||
loaded, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte(main))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte(lower))}}}},
|
||||
})
|
||||
if err != nil || loaded == nil {
|
||||
t.Fatalf("replacement retained obsolete identity: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAncestorRemovalCanEstablishPendingReplacementIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, inheritance := range []bool{false, true} {
|
||||
name := "tiers"
|
||||
if inheritance {
|
||||
name = "inheritance"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var loaded *testResult[map[string]any]
|
||||
var err error
|
||||
if inheritance {
|
||||
loaded, err = testLoad[map[string]any](Bytes("source", []byte(`configkit:
|
||||
sequences:
|
||||
- {destination: [base, group, items], key: id}
|
||||
- {destination: [child, group, items], key: name}
|
||||
base: {group: {items: [{id: old}]}}
|
||||
child:
|
||||
extends: base
|
||||
group: null
|
||||
grandchild:
|
||||
extends: child
|
||||
group: {items: [{name: new}]}
|
||||
`)))
|
||||
} else {
|
||||
loaded, err = testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte("group: {items: [{name: new}]}\n"))},
|
||||
underlays: []loadGroup{
|
||||
{inputs: []loadInput{{source: Bytes("base", []byte("configkit:\n sequences: [{destination: [group, items], key: id}]\ngroup: {items: [{id: old}]}\n"))}}},
|
||||
{inputs: []loadInput{{source: Bytes("remove", []byte("configkit:\n sequences: [{destination: [group, items], key: name}]\ngroup: null\n"))}}},
|
||||
},
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value := loaded.Config
|
||||
if inheritance {
|
||||
value = value["grandchild"].(map[string]any)
|
||||
}
|
||||
items := value["group"].(map[string]any)["items"].([]any)
|
||||
if len(items) != 1 || items[0].(map[string]any)["name"] != "new" {
|
||||
t.Fatalf("reintroduced collection = %#v", items)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInheritanceAncestorRetirementDoesNotResurrectOldIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := testLoad[map[string]any](Bytes("source", []byte(`configkit:
|
||||
sequences: [{destination: [base, group, items], key: id}]
|
||||
base: {group: {items: [{id: old}]}}
|
||||
child:
|
||||
extends: base
|
||||
group: null
|
||||
grandchild:
|
||||
extends: child
|
||||
group: {items: [{name: new}]}
|
||||
`)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplacementRetirementPreservesApplicationConstraintsAndRetainedKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, withValue := range []bool{false, true} {
|
||||
main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\n"
|
||||
if withValue {
|
||||
main += "items: null\n"
|
||||
}
|
||||
definition := Definition[map[string]any]{}
|
||||
if withValue {
|
||||
definition.sequenceIdentityRegistrations = []sequenceIdentityRegistration{{
|
||||
selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"},
|
||||
}}
|
||||
}
|
||||
_, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte(main))}, definition: definition,
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"))}}}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 || loadErr.Diagnostics()[0].Code != CodeSequenceConflict {
|
||||
t.Fatalf("retirement bypassed identity agreement: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user