Define private import defaults and declaration parsing
This commit is contained in:
243
internal/engine/import_spec_test.go
Normal file
243
internal/engine/import_spec_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user