Audit registered collection changes by identity
This commit is contained in:
@@ -72,3 +72,118 @@ func TestResolutionComparisonKeepsDiffAtomic(t *testing.T) {
|
||||
t.Fatalf("atomic deltas = %#v", atomic)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionAuditsRegisteredCollectionChanges(t *testing.T) {
|
||||
type item struct {
|
||||
ID string `yaml:"id"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
move, container, rename bool
|
||||
wantPath string
|
||||
}{
|
||||
{name: "field edit"},
|
||||
{name: "uncovered move", move: true, wantPath: "/items"},
|
||||
{name: "covered move", move: true, container: true},
|
||||
{name: "identity replacement", rename: true, wantPath: "/items"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
definition := Definition[config]{Resolve: func(value *config, r *Resolution) error {
|
||||
value.Items[0].Value = "changed"
|
||||
if test.move {
|
||||
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||||
}
|
||||
if test.rename {
|
||||
value.Items[0].ID = "new"
|
||||
}
|
||||
index := 0
|
||||
if test.move {
|
||||
index = 1
|
||||
}
|
||||
r.RecordNormalization(Keys("items").Index(index).Key("value"), "change value")
|
||||
if test.container {
|
||||
r.RecordNormalization(Keys("items"), "reorder")
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
definition = DefinitionWithSequenceIdentityRegistrations(definition, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
|
||||
result, err := testLoad(Bytes("items", []byte("items: [{id: a, value: old}, {id: b, value: other}]\n")), Options[config]{Definition: definition})
|
||||
if test.wantPath == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.move {
|
||||
for _, snapshot := range result.history.resolutionPayload.snapshots {
|
||||
if snapshot.change.Path.String() == "/items/1/value" && (snapshot.before == nil || snapshot.before.Text != "old") {
|
||||
t.Fatalf("paired before = %#v", snapshot.before)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
var failure *LoadError
|
||||
if !errors.As(err, &failure) {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, diagnostic := range failure.Diagnostics() {
|
||||
if diagnostic.Code == CodeResolveUnrecordedChange && diagnostic.Path.String() == test.wantPath {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisteredResolutionFallbackAndHelperTarget(t *testing.T) {
|
||||
type item struct {
|
||||
ID string `yaml:"id"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
registration := []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}}
|
||||
makeDefinition := func(resolve func(*config, *Resolution) error) Definition[config] {
|
||||
return DefinitionWithSequenceIdentityRegistrations(Definition[config]{Defaults: func() config { return config{Items: []item{{ID: "a"}, {ID: "a"}}} }, Resolve: resolve}, registration)
|
||||
}
|
||||
_, err := testLoad(NoSource(), Options[config]{Definition: makeDefinition(func(value *config, r *Resolution) error {
|
||||
value.Items[1].ID = "b"
|
||||
r.RecordNormalization(Keys("items"), "repair identities")
|
||||
return nil
|
||||
})})
|
||||
if err != nil {
|
||||
t.Fatalf("repair rejected: %v", err)
|
||||
}
|
||||
_, err = testLoad(NoSource(), Options[config]{Definition: makeDefinition(func(*config, *Resolution) error { return nil })})
|
||||
if err == nil {
|
||||
t.Fatal("invalid final identities accepted")
|
||||
}
|
||||
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
|
||||
Normalize(r, Keys("items").Index(0).Key("value"), &value.Items[0].Value, "changed", "normalize")
|
||||
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||||
r.RecordNormalization(Keys("items"), "reorder")
|
||||
return nil
|
||||
}}, registration)
|
||||
_, err = testLoad(Bytes("items", []byte("items: [{id: a, value: first}, {id: b, value: second}]\n")), Options[config]{Definition: definition})
|
||||
var failure *LoadError
|
||||
if !errors.As(err, &failure) {
|
||||
t.Fatalf("helper error = %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, diagnostic := range failure.Diagnostics() {
|
||||
if diagnostic.Code == CodeResolveInvalidTarget && diagnostic.Path.String() == "/items/0/value" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user