Track the main version through sequence movement
This commit is contained in:
197
facade_sequence_version_test.go
Normal file
197
facade_sequence_version_test.go
Normal file
@@ -0,0 +1,197 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user