Files
ConfigKit/internal/engine/collection_retirement_test.go

112 lines
3.7 KiB
Go

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)
}
}
}