Activate selected overlay collection policies
This commit is contained in:
@@ -94,6 +94,263 @@ value: scalar
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSelectedOverlayAppliesCollectionOperationsAndEvidence(t *testing.T) {
|
||||
t.Parallel()
|
||||
type item struct {
|
||||
ID string `yaml:"id"`
|
||||
Value string `yaml:"value"`
|
||||
Children []item `yaml:"children"`
|
||||
}
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
mode string
|
||||
overlay string
|
||||
want []item
|
||||
}{
|
||||
{
|
||||
name: "append",
|
||||
mode: "append",
|
||||
overlay: `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: append
|
||||
items: [{id: c, value: added}]
|
||||
`,
|
||||
want: []item{{ID: "a", Value: "old"}, {ID: "b", Value: "kept"}, {ID: "c", Value: "added"}},
|
||||
},
|
||||
{
|
||||
name: "upsert replaces complete records at retained positions",
|
||||
mode: "upsert",
|
||||
overlay: `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: upsert
|
||||
items: [{id: a, value: new}]
|
||||
`,
|
||||
want: []item{{ID: "a", Value: "new"}, {ID: "b", Value: "kept"}},
|
||||
},
|
||||
{
|
||||
name: "replace",
|
||||
mode: "replace",
|
||||
overlay: `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: replace
|
||||
items: [{id: c, value: replacement}]
|
||||
`,
|
||||
want: []item{{ID: "c", Value: "replacement"}},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: a, value: old, children: [{id: child}]}, {id: b, value: kept}]\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", test.overlay)
|
||||
result, err := testLoad[config](File(mainPath), Options[config]{Overlay: "testing"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(result.Config.Items) != len(test.want) {
|
||||
t.Fatalf("items = %#v, want %#v", result.Config.Items, test.want)
|
||||
}
|
||||
for index, want := range test.want {
|
||||
if result.Config.Items[index].ID != want.ID || result.Config.Items[index].Value != want.Value {
|
||||
t.Fatalf("items[%d] = %#v, want id %q value %q", index, result.Config.Items[index], want.ID, want.Value)
|
||||
}
|
||||
}
|
||||
if _, found := result.Metadata().Lookup(Keys("configkit")); found {
|
||||
t.Fatal("overlay collection controls reached application metadata")
|
||||
}
|
||||
if test.mode == "append" {
|
||||
base, _ := result.Metadata().Lookup(Keys("items").Index(0).Key("value"))
|
||||
added, _ := result.Metadata().Lookup(Keys("items").Index(2).Key("value"))
|
||||
if base.Origin.Layer != LayerBase || added.Origin.Layer != LayerOverlay {
|
||||
t.Fatalf("append origins = base %#v added %#v", base.Origin, added.Origin)
|
||||
}
|
||||
history := result.Metadata().History(Keys("items").Index(2).Key("value"))
|
||||
if len(history) == 0 || history[len(history)-1].Origin.Layer != LayerOverlay {
|
||||
t.Fatalf("append history = %#v", history)
|
||||
}
|
||||
}
|
||||
if test.mode == "upsert" {
|
||||
if _, found := result.Metadata().Lookup(Keys("items").Index(0).Key("children").Index(0)); found {
|
||||
t.Fatal("upsert retained a discarded child")
|
||||
}
|
||||
history := result.Metadata().History(Keys("items").Index(0).Key("children").Index(0))
|
||||
if len(history) < 2 {
|
||||
t.Fatalf("discarded child history = %#v", history)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
requireCode := func(t *testing.T, main, overlay string, code Code) {
|
||||
t.Helper()
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", main)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", overlay)
|
||||
_, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
var loadError *LoadError
|
||||
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != code {
|
||||
t.Fatalf("load error = %#v, want %s", err, code)
|
||||
}
|
||||
}
|
||||
t.Run("key only validates retained records", func(t *testing.T) {
|
||||
requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
`, CodeSequenceConflict)
|
||||
})
|
||||
|
||||
t.Run("mode only retains lower identity", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: append
|
||||
items: [{id: base}]
|
||||
`)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
mode: append
|
||||
items: [{id: overlay}]
|
||||
`)
|
||||
result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}, {ID: "overlay"}}) {
|
||||
t.Fatalf("items = %#v", result.Config.Items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("lower explicit mode remains active when overlay omits it", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: append
|
||||
items: [{id: base}]
|
||||
`)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "items: [{id: overlay}]\n")
|
||||
result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
if err != nil || !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}, {ID: "overlay"}}) {
|
||||
t.Fatalf("retained operation = %#v, %v", result, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("overlay identity must agree with retained identity", func(t *testing.T) {
|
||||
requireCode(t, `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
items: [{id: base}]
|
||||
`, `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: slug
|
||||
items: [{id: overlay}]
|
||||
`, CodeSequenceConflict)
|
||||
})
|
||||
|
||||
t.Run("replace accepts null and append rejects it", func(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
mode string
|
||||
want Code
|
||||
}{
|
||||
{mode: "replace"},
|
||||
{mode: "append", want: CodeSequenceInvalid},
|
||||
} {
|
||||
overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n"
|
||||
if test.want != "" {
|
||||
requireCode(t, "items: [{id: base}]\n", overlay, test.want)
|
||||
continue
|
||||
}
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: base}]\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", overlay)
|
||||
result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
if err != nil || result.Config.Items != nil {
|
||||
t.Fatalf("replace null = %#v, %v", result, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty contributions and kind mismatches", func(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name, mode, value string
|
||||
want int
|
||||
wantCode Code
|
||||
}{
|
||||
{name: "append empty", mode: "append", value: "[]", want: 1},
|
||||
{name: "upsert empty", mode: "upsert", value: "[]", want: 1},
|
||||
{name: "replace empty", mode: "replace", value: "[]", want: 0},
|
||||
{name: "append mapping", mode: "append", value: "{id: overlay}", wantCode: CodeSequenceInvalid},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n"
|
||||
if test.wantCode != "" {
|
||||
requireCode(t, "items: [{id: base}]\n", overlay, test.wantCode)
|
||||
return
|
||||
}
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: base}]\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", overlay)
|
||||
result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
if err != nil || len(result.Config.Items) != test.want {
|
||||
t.Fatalf("%s result = %#v, %v", test.name, result, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) {
|
||||
t.Parallel()
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: append
|
||||
- destination: [catalog, items]
|
||||
key: id
|
||||
mode: append
|
||||
items: [{id: base}]
|
||||
catalog: {items: [{id: catalog-base}]}
|
||||
`)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `configkit:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
mode: upsert
|
||||
- destination: [catalog, items]
|
||||
mode: append
|
||||
items: [{id: base}]
|
||||
catalog: {items: [{id: catalog-overlay}]}
|
||||
`)
|
||||
result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}}) ||
|
||||
!reflect.DeepEqual(result.Config.Catalog.Items, []ordinarySequenceItem{{ID: "catalog-base"}, {ID: "catalog-overlay"}}) {
|
||||
t.Fatalf("independent overlay policies = items %#v, catalog %#v", result.Config.Items, result.Config.Catalog.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOverlaySelectionUsesOneFixedYAMLFile(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "settings: {kept: base}\n")
|
||||
|
||||
Reference in New Issue
Block a user