Files
configkit/internal/engine/resolution_compare_test.go

206 lines
6.9 KiB
Go

package engine
import (
"errors"
"testing"
)
func TestResolutionAuditsOrdinaryListFields(t *testing.T) {
type item struct {
Count int `yaml:"count"`
Enabled bool `yaml:"enabled"`
Nested []*int `yaml:"nested"`
}
type config struct {
Items []item `yaml:"items"`
}
for _, test := range []struct {
name string
other, resize bool
wantPath string
}{
{name: "covered field"},
{name: "uncovered field", other: true, wantPath: "/items/0/enabled"},
{name: "length change", resize: true, wantPath: "/items"},
} {
t.Run(test.name, func(t *testing.T) {
result, err := testLoad(Bytes("items", []byte("items: [{count: 3, enabled: true, nested: [null]}]\n")), Options[config]{Definition: Definition[config]{
Resolve: func(value *config, r *Resolution) error {
value.Items[0].Count = 0
r.RecordNormalization(Keys("items").Index(0).Key("count"), "clear count")
if test.other {
value.Items[0].Enabled = false
}
if test.resize {
value.Items = append(value.Items, item{})
}
return nil
},
}})
if test.wantPath == "" {
if err != nil || result.Config.Items[0].Count != 0 {
t.Fatalf("result = %#v, error = %v", result, err)
}
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 TestResolutionComparisonKeepsDiffAtomic(t *testing.T) {
before := parseCompositionDocument(t, "before", "items: [{values: [true, null]}]")
after := parseCompositionDocument(t, "after", "items: [{values: [false, 0]}]")
deltas := compareResolutionValues(before, after)
if len(deltas) != 2 || deltas[0].path.String() != "/items/0/values/0" || deltas[1].path.String() != "/items/0/values/1" {
t.Fatalf("resolution deltas = %#v", deltas)
}
atomic := compareConfigurationValues(before, after)
if len(atomic) != 1 || atomic[0].path.String() != "/items" {
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 {
correspondence := result.history.resolutionPayload.correspondence
entry, ok := correspondence.entryPath(Keys("items").Index(1).Key("value"))
if !ok || entry.String() != "/items/0/value" {
t.Fatalf("retained entry = %s, %t", entry.String(), ok)
}
for _, snapshot := range result.history.resolutionPayload.snapshots {
if snapshot.change.Path.String() == "/items/1/value" && (snapshot.before == nil || snapshot.before.Text != "old" || !snapshot.hasEntry || snapshot.entryPath.String() != "/items/0/value") {
t.Fatalf("paired before = %#v", snapshot.before)
}
if snapshot.before != nil {
found := false
for _, root := range result.history.documentRoots() {
if root == snapshot.before {
found = true
}
}
if !found {
t.Fatal("paired snapshot omitted from retained roots")
}
}
}
}
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())
}
}