Files
ConfigKit/facade_sequence_version_test.go

198 lines
8.3 KiB
Go

package configkit_test
import (
"fmt"
"testing"
"testing/fstest"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
type movedVersionItem struct {
ID string `yaml:"id"`
Version string `yaml:"version"`
}
type movedVersionCatalog struct {
Items []movedVersionItem `yaml:"items"`
}
func TestMainVersionDecodingFollowsSequenceMovement(t *testing.T) {
t.Parallel()
for _, mounted := range []bool{false, true} {
for _, matching := range []bool{false, true} {
for _, version := range []string{"4", "'4'"} {
t.Run(fmt.Sprintf("mounted=%t/matched=%t/%s", mounted, matching, version), func(t *testing.T) {
body := "items: [{id: main, version: " + version + "}]\n"
path := configkit.Keys("items").Index(0).Key("version")
main := configkit.Input{}
if mounted {
body, path = "id: main\nversion: "+version+"\n", configkit.Keys("version")
main.Destination, main.PlacementMode = configkit.Keys("items"), configkit.PlacementModeSequenceItem
}
main.Source = configkit.Bytes("main", []byte(body))
keep := "{id: keep}"
if mounted {
keep = "{id: keep, version: ordinary}"
}
lower := "items: [" + keep + "]\n"
if matching {
lower = "items: [" + keep + ", {id: main}]\n"
}
calls := 0
loaded, err := configkit.LoadLayered(main, configkit.LayeredOptions[movedVersionCatalog]{
SourceVersionMode: configkit.SourceVersionModeIfPresent,
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Bytes("lower", []byte(lower))}}}},
Definition: configkit.Definition[movedVersionCatalog]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
Version: &configkit.VersionRule{Path: path, Check: func(version configkit.Version, _ *configkit.Validation) error {
calls++
if !version.Present || version.Text != "4" {
t.Errorf("checked version = %#v", version)
}
return nil
}},
},
})
if err != nil {
t.Fatal(err)
}
if calls != 1 || len(loaded.Config.Items) != 2 || loaded.Config.Items[1].ID != "main" || loaded.Config.Items[1].Version != "4" {
t.Fatalf("moved main = %#v, checker calls = %d", loaded.Config, calls)
}
if mounted && loaded.Config.Items[0].Version != "ordinary" {
t.Fatal("unrelated underlay payload was treated as the main declaration")
}
for _, source := range loaded.Sources() {
if source.Layer == configkit.LayerBase && source.VersionCheck.Path.String() != path.String() {
t.Fatal("effective movement changed source-local version evidence")
}
}
})
}
}
}
}
func TestOverlayVersionProtectionUsesAssignedRecordPositions(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name, payload string
code configkit.Code
}{
{name: "change main header", payload: "[{id: main, version: changed}]", code: configkit.CodeOverlayVersion},
{name: "remove main header", payload: "[{id: main}]", code: configkit.CodeOverlayVersion},
{name: "other record version", payload: "[{id: keep, version: ordinary}]"},
{name: "new record version", payload: "[{id: new, version: ordinary}]"},
{name: "only main header gets integer conversion", payload: "[{id: new, version: 5}]", code: configkit.CodeDecodeType},
} {
t.Run(test.name, func(t *testing.T) {
files := configkit.NewFilesystem("catalog", fstest.MapFS{
"overlays/test.yml": {Data: []byte("items: " + test.payload + "\n")},
})
calls := 0
loaded, err := configkit.LoadLayered(configkit.Input{
Source: configkit.Bytes("main", []byte("id: main\nversion: 4\n")), Destination: configkit.Keys("items"), PlacementMode: configkit.PlacementModeSequenceItem,
}, configkit.LayeredOptions[movedVersionCatalog]{
SourceVersionMode: configkit.SourceVersionModeIfPresent,
Overlay: "test", OverlayFilesystem: &configkit.FilesystemContext{FS: &files, BaseDir: ".", Root: "."},
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Bytes("lower", []byte("items: [{id: keep}]\n"))}}}},
Definition: configkit.Definition[movedVersionCatalog]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { calls++; return nil }},
},
})
if calls != 1 {
t.Fatalf("checker calls = %d", calls)
}
if test.code != "" {
loadErr := requireFacadeLoadError(t, err)
if loaded != nil || len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != test.code {
t.Fatalf("version diagnostics = %#v", loadErr.Diagnostics())
}
return
}
if err != nil {
t.Fatal(err)
}
if loaded.Config.Items[1].Version != "4" {
t.Fatal("unrelated overlay changed main header")
}
})
}
}
func TestUnderlayVersionProtectionUsesMainRecordPosition(t *testing.T) {
t.Parallel()
for _, imported := range []bool{false, true} {
t.Run(fmt.Sprintf("imported=%t", imported), func(t *testing.T) {
payload := "items: [{id: keep}, {id: main, version: forbidden}]\n"
lower := configkit.Input{Source: configkit.Bytes("lower", []byte(payload))}
want := configkit.CodeUnderlayVersion
if imported {
files := configkit.NewFilesystem("lower", fstest.MapFS{
"main.yml": {Data: []byte("configkit: {imports: [{path: fragment.yml}]}\n")},
"fragment.yml": {Data: []byte(payload)},
})
lower.Source, lower.Imports = files.File("main.yml"), true
want = configkit.CodeImportVersion
}
calls := 0
result, err := configkit.LoadLayered(configkit.Input{
Source: configkit.Bytes("main", []byte("id: main\nversion: 4\n")),
Destination: configkit.Keys("items"), PlacementMode: configkit.PlacementModeSequenceItem,
}, configkit.LayeredOptions[movedVersionCatalog]{
SourceVersionMode: configkit.SourceVersionModeIfPresent,
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{lower}}},
Definition: configkit.Definition[movedVersionCatalog]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { calls++; return nil }},
},
})
failure := requireFacadeLoadError(t, err)
if result != nil || calls != 1 || len(failure.Diagnostics()) != 1 || failure.Diagnostics()[0].Code != want || failure.Diagnostics()[0].Path.String() != "/items/1/version" {
t.Fatalf("result=%#v calls=%d diagnostics=%#v", result, calls, failure.Diagnostics())
}
})
}
}
func TestMainVersionFollowsInheritedSequenceMovement(t *testing.T) {
t.Parallel()
for _, parentID := range []string{"keep", "main"} {
t.Run(parentID, func(t *testing.T) {
body := "base: {items: [{id: " + parentID + ", version: ordinary}]}\nchild:\n extends: base\n items: [{id: main, version: 4}]\n"
type config struct {
Base, Child movedVersionCatalog
}
calls := 0
result, err := configkit.Load(configkit.Bytes("main", []byte(body)), configkit.Options[config]{
SourceVersionMode: configkit.SourceVersionModeIfPresent,
Definition: configkit.Definition[config]{
SequenceIdentities: []configkit.SequenceIdentity{
configkit.SequenceIdentityAt(configkit.Keys("base", "items"), "id"),
configkit.SequenceIdentityAt(configkit.Keys("child", "items"), "id"),
},
Version: &configkit.VersionRule{Path: configkit.Keys("child", "items").Index(0).Key("version"), Check: func(configkit.Version, *configkit.Validation) error { calls++; return nil }},
},
})
if calls != 1 {
t.Fatalf("checker calls = %d", calls)
}
if parentID == "main" {
failure := requireFacadeLoadError(t, err)
if result != nil || len(failure.Diagnostics()) != 1 || failure.Diagnostics()[0].Code != configkit.CodeInheritanceVersion {
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
}
return
}
if err != nil {
t.Fatal(err)
}
if len(result.Config.Child.Items) != 2 || result.Config.Child.Items[1].Version != "4" || result.Config.Child.Items[0].Version != "ordinary" {
t.Fatalf("inherited items = %#v", result.Config.Child.Items)
}
})
}
}