Files
ConfigKit/internal/engine/destination_import_test.go

327 lines
12 KiB
Go

package engine
import (
"path/filepath"
"testing"
"testing/fstest"
)
type importDestinationConfig struct {
Mounted map[string]string `yaml:"mounted"`
Collections map[string]map[string]string `yaml:"collections"`
}
func TestLayeredHostImportsInheritInputDestinationTransitively(t *testing.T) {
t.Parallel()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
imports:
- path: first.yml
- path: second.yml
root: main
`)
firstPath := writeImportFile(t, directory, "first.yml", `configkit:
imports:
- path: nested/third.yml
first: one
`)
thirdPath := writeImportFile(t, directory, "nested/third.yml", "nested: three\n")
secondPath := writeImportFile(t, directory, "second.yml", "second: two\n")
loaded, err := LoadLayered(
Input{Source: File(mainPath), Imports: true, Destination: Keys("mounted")},
testSecretBinding(),
LayeredOptions[importDestinationConfig]{},
)
if err != nil {
if loadError, ok := err.(*LoadError); ok {
t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics())
}
t.Fatalf("LoadLayered: %v", err)
}
result := testLoaded(loaded)
want := map[string]string{"root": "main", "first": "one", "nested": "three", "second": "two"}
if len(result.Config.Mounted) != len(want) {
t.Fatalf("mounted values = %#v", result.Config.Mounted)
}
for key, value := range want {
if result.Config.Mounted[key] != value {
t.Errorf("mounted[%q] = %q, want %q", key, result.Config.Mounted[key], value)
}
}
if _, ok := result.Metadata().Lookup(Keys("mounted", "configkit")); ok {
t.Fatal("source-root import directive leaked into application content")
}
sources := result.Sources()
wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, firstPath), canonicalTestPath(t, thirdPath), canonicalTestPath(t, secondPath)}
if len(sources) != len(wantPaths) {
t.Fatalf("sources = %#v", sources)
}
for index, source := range sources {
if source.Destination.String() != "/mounted" {
t.Errorf("source %d destination = %q", index, source.Destination.String())
}
if source.Path != wantPaths[index] {
t.Errorf("source %d path = %q, want %q", index, source.Path, wantPaths[index])
}
if index > 0 && source.ImportedFrom == nil {
t.Errorf("imported source %d has no physical import location", index)
}
}
}
func TestImportTraversalUsesPerDeclarationDestinations(t *testing.T) {
t.Parallel()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
imports:
- path: parent.yml
- path: sibling.yml
- glob: fragments/*.yml
`)
parentPath := writeImportFile(t, directory, "parent.yml", `configkit:
imports:
- path: nested/child.yml
parent: true
`)
childPath := writeImportFile(t, directory, "nested/child.yml", "child: true\n")
siblingPath := writeImportFile(t, directory, "sibling.yml", "sibling: true\n")
alphaPath := writeImportFile(t, directory, "fragments/a.yml", "alpha: true\n")
betaPath := writeImportFile(t, directory, "fragments/b.yml", "beta: true\n")
main := parseImportMain(t, mainPath)
_, declarations, diagnostics := splitImportDirectives(main.document)
if len(diagnostics) != 0 {
t.Fatalf("main directive diagnostics = %#v", diagnostics)
}
declarations[0].destination = Keys("parent")
declarations[1].destination = Keys("sibling")
declarations[2].destination = Keys("collection")
access, err := openHostAccess(directory)
if err != nil {
t.Fatal(err)
}
defer func() { _ = access.close() }()
state := importTraversal{
access: access, accounting: &importAccounting{}, limits: defaultImportLimits(),
active: make(map[resourceIdentity]int), visited: make(map[resourceIdentity]importRoute),
}
if diagnostics := state.walkDeclarations(directory, declarations, 0, Keys("entry")); len(diagnostics) != 0 {
t.Fatalf("walk diagnostics = %#v", diagnostics)
}
wantSources := []struct {
path string
destination string
}{
{path: parentPath, destination: "/entry/parent"},
{path: childPath, destination: "/entry/parent"},
{path: siblingPath, destination: "/entry/sibling"},
{path: alphaPath, destination: "/entry/collection"},
{path: betaPath, destination: "/entry/collection"},
}
if len(state.sources) != len(wantSources) {
t.Fatalf("import sources = %#v, want %d", state.sources, len(wantSources))
}
for index, want := range wantSources {
got := state.sources[index]
if got.Path != canonicalTestPath(t, want.path) || got.Destination.String() != want.destination {
t.Errorf("source %d = (%q, %q), want (%q, %q)", index, got.Path, got.Destination.String(), canonicalTestPath(t, want.path), want.destination)
}
}
composed, diagnostics := composeDocuments(state.documents...)
if len(diagnostics) != 0 {
t.Fatalf("composition diagnostics = %#v", diagnostics)
}
for _, path := range []Path{
Keys("entry", "parent", "parent"), Keys("entry", "parent", "child"),
Keys("entry", "sibling", "sibling"), Keys("entry", "collection", "alpha"),
Keys("entry", "collection", "beta"),
} {
if _, present := documentAtPath(composed.root, path); !present {
t.Errorf("no composed value at %q", path.String())
}
}
}
func TestEntryDestinationSurvivesImportSetupAndParseFailures(t *testing.T) {
t.Parallel()
t.Run("import context failure", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n")
input, diagnostics := assembleBaseInputTracked(
File(mainPath),
Options[map[string]any]{Imports: true, Filesystem: &FilesystemContext{Root: t.TempDir()}},
Keys("mounted"), &importAccounting{}, nil, nil,
)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot {
t.Fatalf("context diagnostics = %#v", diagnostics)
}
if len(input.sources) != 1 || input.sources[0].Destination.String() != "/mounted" {
t.Fatalf("entry source fallback = %#v", input.sources)
}
})
t.Run("imported parse failure", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: broken.yml}]}\n")
writeImportFile(t, directory, "broken.yml", "broken: [\n")
input, diagnostics := assembleBaseInputTracked(
File(mainPath), Options[map[string]any]{Imports: true},
Keys("mounted"), &importAccounting{}, nil, nil,
)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeYAMLInvalid {
t.Fatalf("parse diagnostics = %#v", diagnostics)
}
if len(input.sources) != 2 {
t.Fatalf("parse-failure sources = %#v", input.sources)
}
for index, source := range input.sources {
if source.Destination.String() != "/mounted" {
t.Errorf("source %d destination = %q, want /mounted", index, source.Destination.String())
}
}
})
}
func TestLayeredNativeFilesystemImportsInheritInputDestination(t *testing.T) {
t.Parallel()
native := fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: first.yml\nroot: main\n")},
"first.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: nested.yml}]}\nfirst: imported\n")},
"nested.yml": &fstest.MapFile{Data: []byte("nested: imported\n")},
}
namespace := newNativeNamespace("bundle", native)
loaded, err := LoadLayered(
Input{Source: filesystemFile(namespace, "main.yml"), Imports: true, Destination: Keys("mounted")},
testSecretBinding(),
LayeredOptions[importDestinationConfig]{},
)
if err != nil {
if loadError, ok := err.(*LoadError); ok {
t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics())
}
t.Fatalf("LoadLayered: %v", err)
}
result := testLoaded(loaded)
if result.Config.Mounted["root"] != "main" || result.Config.Mounted["first"] != "imported" || result.Config.Mounted["nested"] != "imported" {
t.Fatalf("mounted native filesystem values = %#v", result.Config.Mounted)
}
sources := result.Sources()
if len(sources) != 3 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" || sources[2].Destination.String() != "/mounted" {
t.Fatalf("native source destinations = %#v", sources)
}
}
func TestLayeredImportOnlyInputMaterializesDestination(t *testing.T) {
t.Parallel()
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
imports:
- path: optional.yml
optional: true
`)
loaded, err := LoadLayered(
Input{Source: File(mainPath), Imports: true, Destination: Keys("collections", "empty")},
testSecretBinding(),
LayeredOptions[importDestinationConfig]{},
)
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
result := testLoaded(loaded)
info, ok := result.Metadata().Lookup(Keys("collections", "empty"))
if !ok || !info.Explicit {
t.Fatalf("import-only body destination = %#v, %v", info, ok)
}
sources := result.Sources()
if len(sources) != 1 || sources[0].Destination.String() != "/collections/empty" {
t.Fatalf("sources = %#v", sources)
}
}
func TestLayeredImportedVersionClaimsUseEffectiveDestination(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
rulePath Path
destination Path
wantCode Code
}{
{name: "protected effective path", rulePath: Keys("mounted", "version"), destination: Keys("mounted"), wantCode: CodeImportVersion},
{name: "ordinary nested domain version", rulePath: Keys("version"), destination: Keys("collections", "entry")},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
directory := t.TempDir()
writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\n")
writeImportFile(t, directory, "fragment.yml", "version: v2\n")
rule := &VersionRule{Path: test.rulePath, Check: func(Version, *Validation) error { return nil }}
loaded, err := LoadLayered(
Input{Source: NoSource()},
testSecretBinding(),
LayeredOptions[importDestinationConfig]{
Definition: Definition[importDestinationConfig]{Version: rule},
Underlays: []Underlay{{Inputs: []Input{{
Source: File(filepath.Join(directory, "main.yml")), Imports: true, Destination: test.destination,
}}}},
},
)
if test.wantCode != "" {
if err == nil {
t.Fatal("protected imported version claim was accepted")
}
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != test.wantCode {
t.Fatalf("load error = %#v", err)
}
if test.name == "protected effective path" {
sources := loadError.Sources()
if len(sources) != 2 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" {
t.Fatalf("error source destinations = %#v", sources)
}
}
return
}
if err != nil {
t.Fatalf("ordinary nested version was rejected: %v", err)
}
result := testLoaded(loaded)
if got := result.Config.Collections["entry"]["version"]; got != "v2" {
t.Fatalf("nested version = %q", got)
}
})
}
}
func TestLayeredDestinationsDoNotChangeRepeatedSourceIdentity(t *testing.T) {
t.Parallel()
directory := t.TempDir()
writeImportFile(t, directory, "shared.yml", "value: shared\n")
importerPath := writeImportFile(t, directory, "importer.yml", "configkit: {imports: [{path: shared.yml}]}\n")
_, err := LoadLayered(
Input{Source: NoSource()},
testSecretBinding(),
LayeredOptions[importDestinationConfig]{Underlays: []Underlay{{Inputs: []Input{
{Source: File(importerPath), Imports: true, Destination: Keys("imported")},
{Source: File(filepath.Join(directory, "shared.yml")), Destination: Keys("direct")},
}}}},
)
if err == nil {
t.Fatal("the same physical source was accepted at two destinations in one group")
}
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportRepeated {
t.Fatalf("load error = %#v", err)
}
}