261 lines
8.9 KiB
Go
261 lines
8.9 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestOptionalEmptyImportSelectionsRemainSourceLocalAndDetached(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type config struct {
|
|
Value string `yaml:"value"`
|
|
}
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- dir: absent
|
|
- path: missing.yml
|
|
- path: []
|
|
- dir: fragments
|
|
exclude: '*.yml'
|
|
- path: [partial-missing.yml, present.yml]
|
|
- path: []
|
|
required: true
|
|
`)
|
|
writeImportFile(t, directory, "fragments/ignored.yml", "ignored: true\n")
|
|
writeImportFile(t, directory, "present.yml", "value: imported\n")
|
|
|
|
result, err := testLoad[config](File(mainPath), Options[config]{Imports: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Config.Value != "imported" {
|
|
t.Fatalf("value = %q", result.Config.Value)
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 2 {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
wantPaths := []string{"/configkit/imports/0", "/configkit/imports/1", "/configkit/imports/2", "/configkit/imports/3"}
|
|
wantSelectors := []importSelector{importSelectorDirectory, importSelectorPath, importSelectorPath, importSelectorDirectory}
|
|
evidence := sources[0].importSelections
|
|
if len(evidence) != len(wantPaths) {
|
|
t.Fatalf("empty-selection evidence = %#v", evidence)
|
|
}
|
|
for index, selection := range evidence {
|
|
if selection.path.String() != wantPaths[index] || selection.location.SourceID != sources[0].ID ||
|
|
selection.selector != wantSelectors[index] || selection.outcome != importSelectionNone {
|
|
t.Errorf("selection %d = %#v", index, selection)
|
|
}
|
|
}
|
|
if len(sources[1].importSelections) != 0 {
|
|
t.Fatalf("imported source unexpectedly owns declaration evidence: %#v", sources[1])
|
|
}
|
|
|
|
sources[0].importSelections[0].path.segments[0].key = "changed"
|
|
sources[0].importSelections[0].location.SourceID = "changed"
|
|
if got := result.Sources()[0].importSelections[0]; got.path.String() != wantPaths[0] || got.location.SourceID != "source-1" {
|
|
t.Fatalf("mutating a query snapshot changed the retained record: %#v", got)
|
|
}
|
|
other, err := testLoad[config](File(mainPath), Options[config]{Imports: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(other.Sources()[0].importSelections, result.Inspection.Sources()[0].importSelections) {
|
|
t.Fatal("one load's source snapshot affected another load")
|
|
}
|
|
}
|
|
|
|
func TestFailedLoadRetainsOnlyCompletedEmptyImportSelections(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type config struct {
|
|
Value string `yaml:"value"`
|
|
}
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- glob: absent/*.yml
|
|
- path: required-missing.yml
|
|
required: true
|
|
- dir: never-visited
|
|
value: main
|
|
`)
|
|
_, err := testLoad[config](File(mainPath), Options[config]{Imports: true})
|
|
var loadError *LoadError
|
|
if !errors.As(err, &loadError) {
|
|
t.Fatalf("error = %T %v", err, err)
|
|
}
|
|
sources := loadError.Sources()
|
|
if len(sources) != 1 || len(sources[0].importSelections) != 1 {
|
|
t.Fatalf("failed-load sources = %#v", sources)
|
|
}
|
|
selection := sources[0].importSelections[0]
|
|
if selection.path.String() != "/configkit/imports/0" || selection.location.SourceID != sources[0].ID || selection.selector != importSelectorGlob || selection.outcome != importSelectionNone {
|
|
t.Fatalf("retained selection = %#v", selection)
|
|
}
|
|
sources[0].importSelections[0].path.segments[0].key = "changed"
|
|
if got := loadError.Sources()[0].importSelections[0].path.String(); got != "/configkit/imports/0" {
|
|
t.Fatalf("mutating failed-load snapshot changed retained evidence: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestImportSelectionEvidenceSurvivesCompositionUnderlaysAndResolutionEntry(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type item struct {
|
|
ID string `yaml:"id"`
|
|
Metadata map[string]string `yaml:"metadata"`
|
|
}
|
|
type config struct {
|
|
Value string `yaml:"value"`
|
|
Helper string `yaml:"helper"`
|
|
Items []item `yaml:"items"`
|
|
Overlay string `yaml:"overlay"`
|
|
}
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: helper.yml
|
|
- path: item.yml
|
|
destination: [items]
|
|
placement: sequence-item
|
|
- dir: absent-main
|
|
value: main
|
|
`)
|
|
helperPath := writeImportFile(t, directory, "helper.yml", `configkit:
|
|
imports:
|
|
- glob: absent-helper/*.yml
|
|
helper: value
|
|
`)
|
|
itemPath := writeImportFile(t, directory, "item.yml", `configkit:
|
|
imports:
|
|
- path: []
|
|
- path: item-helper.yml
|
|
destination: [metadata]
|
|
id: selected
|
|
`)
|
|
itemHelperPath := writeImportFile(t, directory, "item-helper.yml", `configkit:
|
|
imports:
|
|
- dir: absent-item-helper
|
|
key: value
|
|
`)
|
|
underlayPath := writeImportFile(t, directory, "underlay.yml", `configkit:
|
|
imports:
|
|
- path: []
|
|
value: underlay
|
|
`)
|
|
overlayPath := writeImportFile(t, directory, filepath.Join("overlays", "selected.yml"), "overlay: value\n")
|
|
var resolutionSources map[string]SourceInfo
|
|
loaded, err := LoadLayered(Input{Source: File(mainPath), Imports: true}, testSecretBinding(), LayeredOptions[config]{
|
|
Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
|
resolutionSources = make(map[string]SourceInfo)
|
|
for _, id := range []string{"source-1", "source-2", "source-3", "source-4", "source-5", "source-6"} {
|
|
if source, ok := resolution.Source(id); ok {
|
|
resolutionSources[id] = source
|
|
}
|
|
}
|
|
return nil
|
|
}},
|
|
Underlays: []Underlay{{Inputs: []Input{{Source: File(underlayPath), Imports: true}}}},
|
|
Overlay: "selected", OverlayFilesystem: &FilesystemContext{BaseDir: directory, Root: directory},
|
|
})
|
|
if err != nil {
|
|
if loadError, ok := err.(*LoadError); ok {
|
|
t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
if loaded.Config.Value != "main" || loaded.Config.Overlay != "value" || len(loaded.Config.Items) != 1 || loaded.Config.Items[0].ID != "selected" {
|
|
t.Fatalf("configuration = %#v", loaded.Config)
|
|
}
|
|
want := map[string]string{
|
|
"source-1": "/configkit/imports/2",
|
|
"source-2": "/configkit/imports/0",
|
|
"source-3": "/configkit/imports/0",
|
|
"source-4": "/configkit/imports/0",
|
|
"source-5": "/configkit/imports/0",
|
|
}
|
|
sources := loaded.Inspection.Sources()
|
|
if len(sources) != 6 {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
if sources[1].Path != canonicalTestPath(t, helperPath) || sources[2].Path != canonicalTestPath(t, itemPath) ||
|
|
sources[3].Path != canonicalTestPath(t, itemHelperPath) || sources[4].Path != canonicalTestPath(t, underlayPath) {
|
|
t.Fatalf("acquired source order = %#v", sources)
|
|
}
|
|
if sources[3].destinationUnresolved || sources[3].Destination.String() != "/items/0/metadata" {
|
|
t.Fatalf("nested item helper destination = %#v", sources[3])
|
|
}
|
|
if sources[5].Path != canonicalTestPath(t, overlayPath) || sources[5].Layer != LayerOverlay {
|
|
t.Fatalf("overlay source = %#v", sources[5])
|
|
}
|
|
for id, path := range want {
|
|
var found *SourceInfo
|
|
for index := range sources {
|
|
if sources[index].ID == id {
|
|
found = &sources[index]
|
|
break
|
|
}
|
|
}
|
|
if found == nil || len(found.importSelections) != 1 || found.importSelections[0].path.String() != path || found.importSelections[0].location.SourceID != id {
|
|
t.Errorf("source %s evidence = %#v, want %s", id, found, path)
|
|
}
|
|
if callbackSource, ok := resolutionSources[id]; !ok || len(callbackSource.importSelections) != 1 || callbackSource.importSelections[0].path.String() != path {
|
|
t.Errorf("resolution source %s evidence = %#v, want %s", id, callbackSource, path)
|
|
}
|
|
}
|
|
if len(sources[5].importSelections) != 0 {
|
|
t.Fatalf("overlay unexpectedly has import-selection evidence: %#v", sources[5])
|
|
}
|
|
}
|
|
|
|
func TestFailedNestedItemLoadKeepsEvidenceWithUnresolvedDestination(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type item struct {
|
|
ID string `yaml:"id"`
|
|
}
|
|
type config struct {
|
|
Items []item `yaml:"items"`
|
|
}
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: item.yml
|
|
destination: [items]
|
|
placement: sequence-item
|
|
`)
|
|
writeImportFile(t, directory, "item.yml", `configkit:
|
|
imports:
|
|
- path: helper.yml
|
|
destination: [metadata]
|
|
`)
|
|
writeImportFile(t, directory, "helper.yml", `configkit:
|
|
imports:
|
|
- dir: absent
|
|
- path: missing.yml
|
|
required: true
|
|
key: value
|
|
`)
|
|
_, err := testLoad[config](File(mainPath), Options[config]{Imports: true})
|
|
var loadError *LoadError
|
|
if !errors.As(err, &loadError) {
|
|
t.Fatalf("error = %T %v", err, err)
|
|
}
|
|
for _, source := range loadError.Sources() {
|
|
if source.Path != canonicalTestPath(t, filepath.Join(directory, "helper.yml")) {
|
|
continue
|
|
}
|
|
if !source.destinationUnresolved || source.Destination.String() != "/items" || len(source.importSelections) != 1 ||
|
|
source.importSelections[0].path.String() != "/configkit/imports/0" || source.importSelections[0].location.SourceID != source.ID {
|
|
t.Fatalf("failed nested item source = %#v", source)
|
|
}
|
|
return
|
|
}
|
|
t.Fatalf("helper source missing from failed-load snapshot: %#v", loadError.Sources())
|
|
}
|