198 lines
8.3 KiB
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)
|
|
}
|
|
})
|
|
}
|
|
}
|