211 lines
7.1 KiB
Go
211 lines
7.1 KiB
Go
package engine
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
type destinationLoadConfig struct {
|
|
Main map[string]string `yaml:"main"`
|
|
Defaults map[string]string `yaml:"defaults"`
|
|
Collections map[string]map[string]string `yaml:"collections"`
|
|
Value string `yaml:"value"`
|
|
}
|
|
|
|
type destinationItemConfig struct {
|
|
Catalog struct {
|
|
Entries []map[string]string `yaml:"entries"`
|
|
} `yaml:"catalog"`
|
|
}
|
|
|
|
func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaded, err := LoadLayered[destinationItemConfig](
|
|
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
|
testSecretBinding(),
|
|
LayeredOptions[destinationItemConfig]{Underlays: []Underlay{{Inputs: []Input{{
|
|
Source: Mapping("empty-item", map[string]any{}), Destination: Keys("catalog", "entries"),
|
|
PlacementMode: PlacementModeSequenceItem,
|
|
}}}}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if len(result.Config.Catalog.Entries) != 1 || result.Config.Catalog.Entries[0] == nil || len(result.Config.Catalog.Entries[0]) != 0 {
|
|
t.Fatalf("empty mapping item = %#v", result.Config.Catalog.Entries)
|
|
}
|
|
if _, ok := result.Metadata().Lookup(Keys("catalog", "entries", "0")); ok {
|
|
t.Fatal("sequence index was treated as a literal mapping key during metadata lookup")
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 1 || sources[0].placementMode != PlacementModeSequenceItem || sources[0].destinationUnresolved {
|
|
t.Fatalf("source placement evidence = %#v", sources)
|
|
}
|
|
if got := sources[0].Destination.String(); got != "/catalog/entries" {
|
|
t.Fatalf("source destination = %q", got)
|
|
}
|
|
|
|
absent, err := LoadLayered[destinationItemConfig](
|
|
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
|
testSecretBinding(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NoSource item load: %v", err)
|
|
}
|
|
if got := len(testLoaded(absent).Config.Catalog.Entries); got != 0 {
|
|
t.Fatalf("absent item created %d entries", got)
|
|
}
|
|
}
|
|
|
|
func TestLayeredItemPlacementPreflightsModeAndDestination(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
mode PlacementMode
|
|
path Path
|
|
}{
|
|
{name: "root item", mode: PlacementModeSequenceItem},
|
|
{name: "unknown mode", mode: PlacementMode(255), path: Keys("catalog")},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
reader := bytes.NewBufferString("value: unused\n")
|
|
_, err := LoadLayered[destinationItemConfig](
|
|
Input{Source: Reader("reader", reader), Destination: test.path, PlacementMode: test.mode},
|
|
testSecretBinding(),
|
|
)
|
|
if err == nil || reader.Len() == 0 {
|
|
t.Fatalf("preflight err %v, unread bytes %d", err, reader.Len())
|
|
}
|
|
loadError, ok := err.(*LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeOptionsInvalid {
|
|
t.Fatalf("preflight error = %#v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadOptionsPlaceOneMappingItem(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaded, err := Load[destinationItemConfig](
|
|
Mapping("ordinary", map[string]any{}), testSecretBinding(),
|
|
Options[destinationItemConfig]{Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if len(result.Config.Catalog.Entries) != 1 || result.Config.Catalog.Entries[0] == nil {
|
|
t.Fatalf("ordinary item placement = %#v", result.Config.Catalog.Entries)
|
|
}
|
|
}
|
|
|
|
func TestLayeredLoadPlacesNonImportMainAndUnderlayInputs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
destination := Keys("main")
|
|
main := Input{Source: Bytes("main", []byte("value: primary\n")), Destination: destination}
|
|
underlay := Input{Source: Bytes("defaults", []byte("value: fallback\n")), Destination: Keys("defaults")}
|
|
loaded, err := LoadLayered(main, testSecretBinding(), LayeredOptions[destinationLoadConfig]{
|
|
Underlays: []Underlay{{Inputs: []Input{underlay}}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if result.Config.Main["value"] != "primary" || result.Config.Defaults["value"] != "fallback" {
|
|
t.Fatalf("mounted configuration = %#v", result.Config)
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 2 || sources[0].Destination.String() != "/main" || sources[1].Destination.String() != "/defaults" {
|
|
t.Fatalf("source destinations = %#v", sources)
|
|
}
|
|
|
|
destination.segments[0].key = "caller mutation"
|
|
sources[0].Destination.segments[0].key = "record mutation"
|
|
if got := result.Sources()[0].Destination.String(); got != "/main" {
|
|
t.Fatalf("source destination was not independently owned: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestLayeredLoadOmittedAndEmptyDestinationsKeepRootPlacement(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
inputs := []Input{
|
|
{Source: Bytes("omitted", []byte("value: same\n"))},
|
|
{Source: Bytes("empty", []byte("value: same\n")), Destination: Keys()},
|
|
}
|
|
for _, input := range inputs {
|
|
loaded, err := LoadLayered(input, testSecretBinding(), LayeredOptions[destinationLoadConfig]{})
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if result.Config.Value != "same" {
|
|
t.Fatalf("root value = %q", result.Config.Value)
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 1 || sources[0].Destination.String() != "" {
|
|
t.Fatalf("root source destination = %#v", sources)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLayeredLoadDistinguishesEmptyBodyFromNoSource(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaded, err := LoadLayered(
|
|
Input{Source: NoSource(), Destination: Keys("absent")},
|
|
testSecretBinding(),
|
|
LayeredOptions[destinationLoadConfig]{Underlays: []Underlay{{Inputs: []Input{{
|
|
Source: Bytes("empty", []byte("{}\n")), Destination: Keys("collections", "one"),
|
|
}}}}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
info, ok := result.Metadata().Lookup(Keys("collections", "one"))
|
|
if !ok || !info.Explicit || info.Origin.Layer != LayerUnderlay {
|
|
t.Fatalf("empty mounted mapping = %#v, %v", info, ok)
|
|
}
|
|
if _, ok := result.Metadata().Lookup(Keys("absent")); ok {
|
|
t.Fatal("NoSource materialized its destination")
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 1 || sources[0].Label != "empty" || sources[0].Destination.String() != "/collections/one" {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
}
|
|
|
|
func TestLayeredLoadPreflightsDestinationsBeforeReadingSources(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
reader := bytes.NewBufferString("value: unused\n")
|
|
_, err := LoadLayered(
|
|
Input{Source: NoSource()},
|
|
testSecretBinding(),
|
|
LayeredOptions[destinationLoadConfig]{Underlays: []Underlay{{Inputs: []Input{{
|
|
Source: Reader("reader", reader), Destination: Keys("configkit"),
|
|
}}}}},
|
|
)
|
|
if err == nil {
|
|
t.Fatal("reserved destination was accepted")
|
|
}
|
|
if reader.Len() == 0 {
|
|
t.Fatal("source reader was consumed before destination preflight")
|
|
}
|
|
loadError, ok := err.(*LoadError)
|
|
if !ok || len(loadError.Diagnostics()) != 1 {
|
|
t.Fatalf("error = %#v", err)
|
|
}
|
|
diagnostic := loadError.Diagnostics()[0]
|
|
if diagnostic.Code != CodeOptionsInvalid || diagnostic.Path.String() != "/configkit" || diagnostic.Primary != nil {
|
|
t.Fatalf("destination diagnostic = %#v", diagnostic)
|
|
}
|
|
}
|