244 lines
9.7 KiB
Go
244 lines
9.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseImportDeclarationSpecNormalizesPresenceAndPlacement(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
specs, diagnostics := parseImportSpecsYAML(t, `
|
|
- path: [one.yaml, one.yaml, two.yaml]
|
|
required: false
|
|
destination: [services]
|
|
placement: sequence-item
|
|
- dir: []
|
|
recursive: true
|
|
pattern: []
|
|
exclude: []
|
|
- glob: config/*.yaml
|
|
`)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("diagnostics = %#v", diagnostics)
|
|
}
|
|
if len(specs) != 3 {
|
|
t.Fatalf("spec count = %d, want 3", len(specs))
|
|
}
|
|
first := specs[0]
|
|
if first.selector != importSelectorPath || len(first.paths) != 2 || first.paths[0] != "one.yaml" || first.paths[1] != "two.yaml" {
|
|
t.Fatalf("path selector = %#v", first)
|
|
}
|
|
if first.required == nil || *first.required || first.destination.String() != "/services" || first.placement != placementModeSequenceItem {
|
|
t.Fatalf("explicit declaration values = %#v", first)
|
|
}
|
|
if first.path.String() != "/configkit/imports/0" || first.fieldSites["required"].path.String() != "/configkit/imports/0/required" {
|
|
t.Fatalf("stored paths = declaration %q, required %q", first.path.String(), first.fieldSites["required"].path.String())
|
|
}
|
|
second := specs[1]
|
|
if second.selector != importSelectorDirectory || second.paths == nil || len(second.paths) != 0 || !second.patternSet || second.pattern == nil || len(second.pattern) != 0 || !second.excludeSet || second.exclude == nil {
|
|
t.Fatalf("empty list presence was lost: %#v", second)
|
|
}
|
|
third := specs[2]
|
|
if third.selector != importSelectorGlob || third.glob != "config/*.yaml" || len(third.globSegments) != 2 {
|
|
t.Fatalf("glob selector = %#v", third)
|
|
}
|
|
}
|
|
|
|
func TestParseImportDeclarationSpecValidatesSelectorMatrixAndTypes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name, yaml, path string
|
|
}{
|
|
{name: "missing selector", yaml: " - required: true\n", path: "/configkit/imports/0"},
|
|
{name: "multiple selectors", yaml: " - path: one.yaml\n dir: fragments\n", path: "/configkit/imports/0"},
|
|
{name: "removed optional", yaml: " - path: one.yaml\n optional: true\n", path: "/configkit/imports/0/optional"},
|
|
{name: "inapplicable field", yaml: " - path: one.yaml\n pattern: '*.yaml'\n", path: "/configkit/imports/0/pattern"},
|
|
{name: "selector null", yaml: " - path: null\n", path: "/configkit/imports/0/path"},
|
|
{name: "empty path", yaml: " - path: ''\n", path: "/configkit/imports/0/path"},
|
|
{name: "nested selector list", yaml: " - path: [[one.yaml]]\n", path: "/configkit/imports/0/path/0"},
|
|
{name: "null selector member", yaml: " - dir: [fragments, null]\n", path: "/configkit/imports/0/dir/1"},
|
|
{name: "empty glob", yaml: " - glob: ''\n", path: "/configkit/imports/0/glob"},
|
|
{name: "recursive glob wildcard", yaml: " - glob: '**/*.yaml'\n", path: "/configkit/imports/0/glob"},
|
|
{name: "null boolean", yaml: " - path: one.yaml\n required: null\n", path: "/configkit/imports/0/required"},
|
|
{name: "bad pattern member with empty roots", yaml: " - dir: []\n pattern: ['*', '[']\n", path: "/configkit/imports/0/pattern/1"},
|
|
{name: "bad exclusion member", yaml: " - glob: '*.yaml'\n exclude: ['[']\n", path: "/configkit/imports/0/exclude/0"},
|
|
{name: "invalid destination", yaml: " - path: one.yaml\n destination: [configkit]\n", path: "/configkit/imports/0/destination/0"},
|
|
{name: "invalid placement", yaml: " - path: one.yaml\n placement: sequence\n", path: "/configkit/imports/0/placement"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
_, diagnostics := parseImportSpecsYAML(t, test.yaml)
|
|
if len(diagnostics) == 0 {
|
|
t.Fatal("expected a declaration diagnostic")
|
|
}
|
|
if diagnostics[0].Code != CodeImportInvalid || diagnostics[0].Path.String() != test.path {
|
|
t.Fatalf("diagnostic = %#v, want %s at %s", diagnostics[0], CodeImportInvalid, test.path)
|
|
}
|
|
if diagnostics[0].Primary == nil || diagnostics[0].Primary.Line == 0 {
|
|
t.Fatalf("diagnostic has no source location: %#v", diagnostics[0])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseImportDeclarationSpecValidatesDepthSyntaxAndPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []struct {
|
|
value string
|
|
valid bool
|
|
want int
|
|
}{
|
|
{value: "0", valid: true, want: 0},
|
|
{value: "-0", valid: true, want: 0},
|
|
{value: "999999999999999999999999999999999999", valid: true, want: int(^uint(0) >> 1)},
|
|
{value: "-1"},
|
|
{value: "1.5"},
|
|
{value: "1e3"},
|
|
{value: "'2'"},
|
|
{value: "true"},
|
|
{value: "null"},
|
|
} {
|
|
t.Run(test.value, func(t *testing.T) {
|
|
t.Parallel()
|
|
specs, diagnostics := parseImportSpecsYAML(t, " - dir: []\n recursive: true\n depth: "+test.value+"\n")
|
|
if !test.valid {
|
|
if len(diagnostics) == 0 || diagnostics[0].Path.String() != "/configkit/imports/0/depth" {
|
|
t.Fatalf("diagnostics = %#v, want invalid depth", diagnostics)
|
|
}
|
|
return
|
|
}
|
|
if len(diagnostics) != 0 || len(specs) != 1 || specs[0].depth != test.want {
|
|
t.Fatalf("specs, diagnostics = %#v, %#v; want depth %d", specs, diagnostics, test.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
specs, diagnostics := parseImportSpecsYAML(t, " - dir: []\n depth: 1\n")
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("syntax-only parse diagnostics = %#v", diagnostics)
|
|
}
|
|
_, diagnostics = resolveImportPolicy(specs[0], importDefaults{})
|
|
if len(diagnostics) != 1 || diagnostics[0].Path.String() != "/configkit/imports/0/depth" {
|
|
t.Fatalf("policy diagnostics = %#v, want depth applicability error", diagnostics)
|
|
}
|
|
|
|
recursive := true
|
|
policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{recursive: &recursive})
|
|
if len(diagnostics) != 0 || !policy.recursive || policy.depth != 1 || !policy.depthSet {
|
|
t.Fatalf("application-default policy, diagnostics = %#v, %#v", policy, diagnostics)
|
|
}
|
|
}
|
|
|
|
func TestResolveImportPolicyAppliesDefaultsIndependently(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
omittedSpecs, diagnostics := parseImportSpecsYAML(t, " - dir: fragments\n")
|
|
if len(diagnostics) != 0 {
|
|
t.Fatal(diagnostics)
|
|
}
|
|
omitted, diagnostics := resolveImportPolicy(omittedSpecs[0], importDefaults{})
|
|
if len(diagnostics) != 0 || omitted.required || omitted.recursive || !omitted.follow || !omitted.hidden || omitted.pattern != nil {
|
|
t.Fatalf("library defaults = %#v, diagnostics = %#v", omitted, diagnostics)
|
|
}
|
|
|
|
specs, diagnostics := parseImportSpecsYAML(t, `
|
|
- dir: fragments
|
|
required: false
|
|
recursive: false
|
|
follow: false
|
|
hidden: false
|
|
pattern: []
|
|
`)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatal(diagnostics)
|
|
}
|
|
appRequired, appRecursive, appFollow, appHidden := true, true, true, true
|
|
defaults := importDefaults{
|
|
required: &appRequired,
|
|
recursive: &appRecursive,
|
|
follow: &appFollow,
|
|
hidden: &appHidden,
|
|
pattern: []string{"*.yaml"},
|
|
}
|
|
policy, diagnostics := resolveImportPolicy(specs[0], defaults)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("policy diagnostics = %#v", diagnostics)
|
|
}
|
|
if policy.required || policy.recursive || policy.follow || policy.hidden || policy.pattern == nil || len(policy.pattern) != 0 {
|
|
t.Fatalf("explicit values did not override application defaults: %#v", policy)
|
|
}
|
|
|
|
pathSpecs, diagnostics := parseImportSpecsYAML(t, " - path: file.yaml\n")
|
|
if len(diagnostics) != 0 {
|
|
t.Fatal(diagnostics)
|
|
}
|
|
pathPolicy, diagnostics := resolveImportPolicy(pathSpecs[0], defaults)
|
|
if len(diagnostics) != 0 || pathPolicy.required != true || pathPolicy.recursive || pathPolicy.pattern != nil {
|
|
t.Fatalf("path policy applied unsupported defaults: %#v, %#v", pathPolicy, diagnostics)
|
|
}
|
|
defaulted, diagnostics := resolveImportPolicy(omittedSpecs[0], defaults)
|
|
if len(diagnostics) != 0 || len(defaulted.pattern) != 1 || defaulted.pattern[0] != "*.yaml" {
|
|
t.Fatalf("directory pattern default = %#v, diagnostics = %#v", defaulted.pattern, diagnostics)
|
|
}
|
|
|
|
snapshot := snapshotImportDefaults(defaults)
|
|
*defaults.required = false
|
|
defaults.pattern[0] = "*.toml"
|
|
if snapshot.required == nil || !*snapshot.required || snapshot.pattern[0] != "*.yaml" {
|
|
t.Fatalf("snapshot shares mutable inputs: %#v", snapshot)
|
|
}
|
|
empty := snapshotImportDefaults(importDefaults{pattern: []string{}})
|
|
if empty.pattern == nil || len(empty.pattern) != 0 {
|
|
t.Fatalf("snapshot lost explicit empty pattern: %#v", empty)
|
|
}
|
|
if snapshotImportDefaults(importDefaults{}).pattern != nil {
|
|
t.Fatal("snapshot changed omitted pattern into explicit empty")
|
|
}
|
|
}
|
|
|
|
func TestInvalidImportDefaultPatternsFailBeforeReadingSource(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
reader := &countingImportSpecReader{Reader: strings.NewReader("not: read\n")}
|
|
_, err := Load(Reader("invalid-default-pattern.yml", reader), testSecretBinding(), Options[struct{}]{
|
|
importDefaults: importDefaults{pattern: []string{"["}},
|
|
})
|
|
var loadErr *LoadError
|
|
if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 || loadErr.Diagnostics()[0].Code != CodeOptionsInvalid {
|
|
t.Fatalf("load error = %T %v, want invalid-options LoadError", err, err)
|
|
}
|
|
if reader.reads != 0 {
|
|
t.Fatalf("reader consumed %d bytes before invalid defaults failed", reader.reads)
|
|
}
|
|
}
|
|
|
|
func parseImportSpecsYAML(t *testing.T, declarations string) ([]importDeclarationSpec, []Diagnostic) {
|
|
t.Helper()
|
|
parsed, diagnostics := parseSource(Bytes("import specification fixture", []byte("configkit:\n imports:\n"+declarations)), "source-1", defaultParseLimits())
|
|
if len(diagnostics) != 0 {
|
|
return nil, diagnostics
|
|
}
|
|
index := documentEntryIndex(parsed.document.Mapping, directiveKey)
|
|
if index < 0 {
|
|
t.Fatal("missing root directive in test fixture")
|
|
}
|
|
return parseImportSpecificationDirective(parsed.document.Mapping[index].Value, Keys(directiveKey))
|
|
}
|
|
|
|
type countingImportSpecReader struct {
|
|
io.Reader
|
|
reads int
|
|
}
|
|
|
|
func (reader *countingImportSpecReader) Read(buffer []byte) (int, error) {
|
|
read, err := reader.Reader.Read(buffer)
|
|
reader.reads += read
|
|
return read, err
|
|
}
|