mirror of https://github.com/databricks/cli.git
564 lines
15 KiB
Go
564 lines
15 KiB
Go
package template
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"testing"
|
|
"text/template"
|
|
|
|
"github.com/databricks/cli/libs/jsonschema"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTemplateConfigAssignValuesFromFile(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-file"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
err = c.assignValuesFromFile(filepath.Join(testDir, "config.json"))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(1), c.values["int_val"])
|
|
assert.Equal(t, float64(2), c.values["float_val"])
|
|
assert.Equal(t, true, c.values["bool_val"])
|
|
assert.Equal(t, "hello", c.values["string_val"])
|
|
}
|
|
}
|
|
|
|
func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-file"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
c.values = map[string]any{
|
|
"string_val": "this-is-not-overwritten",
|
|
}
|
|
|
|
err = c.assignValuesFromFile(filepath.Join(testDir, "config.json"))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(1), c.values["int_val"])
|
|
assert.Equal(t, float64(2), c.values["float_val"])
|
|
assert.Equal(t, true, c.values["bool_val"])
|
|
assert.Equal(t, "this-is-not-overwritten", c.values["string_val"])
|
|
}
|
|
}
|
|
|
|
func TestTemplateConfigAssignValuesFromFileForInvalidIntegerValue(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-file-invalid-int"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
err = c.assignValuesFromFile(filepath.Join(testDir, "config.json"))
|
|
assert.EqualError(t, err, fmt.Sprintf("failed to load config from file %s: failed to parse property int_val: cannot convert \"abc\" to an integer", filepath.Join(testDir, "config.json")))
|
|
}
|
|
|
|
func TestTemplateConfigAssignValuesFromFileFiltersPropertiesNotInTheSchema(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-file-unknown-property"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
err = c.assignValuesFromFile(filepath.Join(testDir, "config.json"))
|
|
assert.NoError(t, err)
|
|
|
|
// assert only the known property is loaded
|
|
assert.Len(t, c.values, 1)
|
|
assert.Equal(t, "i am a known property", c.values["string_val"])
|
|
}
|
|
|
|
func TestTemplateConfigAssignValuesFromDefaultValues(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-default-value"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
r, err := newRenderer(ctx, nil, nil, "./testdata/empty/template", "./testdata/empty/library", t.TempDir())
|
|
require.NoError(t, err)
|
|
|
|
err = c.assignDefaultValues(r)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(123), c.values["int_val"])
|
|
assert.Equal(t, float64(123), c.values["float_val"])
|
|
assert.Equal(t, true, c.values["bool_val"])
|
|
assert.Equal(t, "hello", c.values["string_val"])
|
|
}
|
|
}
|
|
|
|
func TestTemplateConfigAssignValuesFromTemplatedDefaultValues(t *testing.T) {
|
|
testDir := "./testdata/config-assign-from-templated-default-value"
|
|
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
|
|
require.NoError(t, err)
|
|
|
|
r, err := newRenderer(ctx, nil, nil, filepath.Join(testDir, "template/template"), filepath.Join(testDir, "template/library"), t.TempDir())
|
|
require.NoError(t, err)
|
|
|
|
// Note: only the string value is templated.
|
|
// The JSON schema package doesn't allow using a string default for integer types.
|
|
err = c.assignDefaultValues(r)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(123), c.values["int_val"])
|
|
assert.Equal(t, float64(123), c.values["float_val"])
|
|
assert.Equal(t, true, c.values["bool_val"])
|
|
assert.Equal(t, "world", c.values["string_val"])
|
|
}
|
|
}
|
|
|
|
func TestTemplateConfigValidateValuesDefined(t *testing.T) {
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, "testdata/config-test-schema/test-schema.json")
|
|
require.NoError(t, err)
|
|
|
|
c.values = map[string]any{
|
|
"int_val": 1,
|
|
"float_val": 1.0,
|
|
"bool_val": false,
|
|
}
|
|
|
|
err = c.validate()
|
|
assert.EqualError(t, err, "validation for template input parameters failed. no value provided for required property string_val")
|
|
}
|
|
|
|
func TestTemplateConfigValidateTypeForValidConfig(t *testing.T) {
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, "testdata/config-test-schema/test-schema.json")
|
|
require.NoError(t, err)
|
|
|
|
c.values = map[string]any{
|
|
"int_val": 1,
|
|
"float_val": 1.1,
|
|
"bool_val": true,
|
|
"string_val": "abcd",
|
|
}
|
|
|
|
err = c.validate()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestTemplateConfigValidateTypeForUnknownField(t *testing.T) {
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, "testdata/config-test-schema/test-schema.json")
|
|
require.NoError(t, err)
|
|
|
|
c.values = map[string]any{
|
|
"unknown_prop": 1,
|
|
"int_val": 1,
|
|
"float_val": 1.1,
|
|
"bool_val": true,
|
|
"string_val": "abcd",
|
|
}
|
|
|
|
err = c.validate()
|
|
assert.EqualError(t, err, "validation for template input parameters failed. property unknown_prop is not defined in the schema")
|
|
}
|
|
|
|
func TestTemplateConfigValidateTypeForInvalidType(t *testing.T) {
|
|
ctx := context.Background()
|
|
c, err := newConfig(ctx, "testdata/config-test-schema/test-schema.json")
|
|
require.NoError(t, err)
|
|
|
|
c.values = map[string]any{
|
|
"int_val": "this-should-be-an-int",
|
|
"float_val": 1.1,
|
|
"bool_val": true,
|
|
"string_val": "abcd",
|
|
}
|
|
|
|
err = c.validate()
|
|
assert.EqualError(t, err, "validation for template input parameters failed. incorrect type for property int_val: expected type integer, but value is \"this-should-be-an-int\"")
|
|
}
|
|
|
|
func TestTemplateValidateSchema(t *testing.T) {
|
|
var err error
|
|
toSchema := func(s string) *jsonschema.Schema {
|
|
return &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"foo": {
|
|
Type: jsonschema.Type(s),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
err = validateSchema(toSchema("string"))
|
|
assert.NoError(t, err)
|
|
|
|
err = validateSchema(toSchema("boolean"))
|
|
assert.NoError(t, err)
|
|
|
|
err = validateSchema(toSchema("number"))
|
|
assert.NoError(t, err)
|
|
|
|
err = validateSchema(toSchema("integer"))
|
|
assert.NoError(t, err)
|
|
|
|
err = validateSchema(toSchema("object"))
|
|
assert.EqualError(t, err, "property type object is not supported by bundle templates")
|
|
|
|
err = validateSchema(toSchema("array"))
|
|
assert.EqualError(t, err, "property type array is not supported by bundle templates")
|
|
}
|
|
|
|
func TestTemplateValidateSchemaVersion(t *testing.T) {
|
|
version := latestSchemaVersion
|
|
schema := jsonschema.Schema{
|
|
Extension: jsonschema.Extension{
|
|
Version: &version,
|
|
},
|
|
}
|
|
assert.NoError(t, validateSchema(&schema))
|
|
|
|
version = latestSchemaVersion + 1
|
|
schema = jsonschema.Schema{
|
|
Extension: jsonschema.Extension{
|
|
Version: &version,
|
|
},
|
|
}
|
|
assert.EqualError(t, validateSchema(&schema), fmt.Sprintf("template schema version %d is not supported by this version of the CLI. Please upgrade your CLI to the latest version", version))
|
|
|
|
version = 5000
|
|
schema = jsonschema.Schema{
|
|
Extension: jsonschema.Extension{
|
|
Version: &version,
|
|
},
|
|
}
|
|
assert.EqualError(t, validateSchema(&schema), "template schema version 5000 is not supported by this version of the CLI. Please upgrade your CLI to the latest version")
|
|
|
|
version = 0
|
|
schema = jsonschema.Schema{
|
|
Extension: jsonschema.Extension{
|
|
Version: &version,
|
|
},
|
|
}
|
|
assert.NoError(t, validateSchema(&schema))
|
|
}
|
|
|
|
func TestTemplateEnumValidation(t *testing.T) {
|
|
schema := jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Type: "integer",
|
|
Enum: []any{1, 2, 3, 4},
|
|
},
|
|
},
|
|
}
|
|
|
|
c := &config{
|
|
schema: &schema,
|
|
values: map[string]any{
|
|
"abc": 5,
|
|
},
|
|
}
|
|
assert.EqualError(t, c.validate(), "validation for template input parameters failed. expected value of property abc to be one of [1 2 3 4]. Found: 5")
|
|
|
|
c = &config{
|
|
schema: &schema,
|
|
values: map[string]any{
|
|
"abc": 4,
|
|
},
|
|
}
|
|
assert.NoError(t, c.validate())
|
|
}
|
|
|
|
func TestTemplateSchemaErrorsWithEmptyDescription(t *testing.T) {
|
|
_, err := newConfig(context.Background(), "./testdata/config-test-schema/invalid-test-schema.json")
|
|
assert.EqualError(t, err, "template property property-without-description is missing a description")
|
|
}
|
|
|
|
func testRenderer() *renderer {
|
|
return &renderer{
|
|
config: map[string]any{
|
|
"fruit": "apples",
|
|
},
|
|
baseTemplate: template.New(""),
|
|
}
|
|
}
|
|
|
|
func TestPromptIsSkippedWhenEmpty(t *testing.T) {
|
|
c := config{
|
|
ctx: context.Background(),
|
|
values: make(map[string]any),
|
|
schema: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"always-skip": {
|
|
Type: "string",
|
|
Default: "I like {{.fruit}}",
|
|
Extension: jsonschema.Extension{
|
|
SkipPromptIf: &jsonschema.Schema{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// We should always skip the prompt here. An empty JSON schema by definition
|
|
// matches all possible configurations.
|
|
skip, err := c.skipPrompt(jsonschema.Property{
|
|
Name: "always-skip",
|
|
Schema: c.schema.Properties["always-skip"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "I like apples", c.values["always-skip"])
|
|
}
|
|
|
|
func TestPromptSkipErrorsWithEmptyDefault(t *testing.T) {
|
|
c := config{
|
|
ctx: context.Background(),
|
|
values: make(map[string]any),
|
|
schema: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"no-default": {
|
|
Type: "string",
|
|
Extension: jsonschema.Extension{
|
|
SkipPromptIf: &jsonschema.Schema{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := c.skipPrompt(jsonschema.Property{
|
|
Name: "no-default",
|
|
Schema: c.schema.Properties["no-default"],
|
|
}, testRenderer())
|
|
assert.EqualError(t, err, "property no-default has skip_prompt_if set but no default value")
|
|
}
|
|
|
|
func TestPromptIsSkippedIfValueIsAssigned(t *testing.T) {
|
|
c := config{
|
|
ctx: context.Background(),
|
|
values: make(map[string]any),
|
|
schema: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"already-assigned": {
|
|
Type: "string",
|
|
Default: "some-default-value",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
c.values["already-assigned"] = "some-value"
|
|
skip, err := c.skipPrompt(jsonschema.Property{
|
|
Name: "already-assigned",
|
|
Schema: c.schema.Properties["already-assigned"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "some-value", c.values["already-assigned"])
|
|
}
|
|
|
|
func TestPromptIsSkipped(t *testing.T) {
|
|
c := config{
|
|
ctx: context.Background(),
|
|
values: make(map[string]any),
|
|
schema: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Type: "string",
|
|
},
|
|
"def": {
|
|
Type: "integer",
|
|
},
|
|
"xyz": {
|
|
Type: "string",
|
|
Default: "hello-world",
|
|
Extension: jsonschema.Extension{
|
|
SkipPromptIf: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Const: "foobar",
|
|
},
|
|
"def": {
|
|
Const: 123,
|
|
},
|
|
},
|
|
Required: []string{"abc", "def"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// No skip condition defined. Prompt should not be skipped.
|
|
skip, err := c.skipPrompt(jsonschema.Property{
|
|
Name: "abc",
|
|
Schema: c.schema.Properties["abc"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
|
|
// No values assigned to config. Prompt should not be skipped.
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
assert.NotContains(t, c.values, "xyz")
|
|
|
|
// Values do not match skip condition. Prompt should not be skipped.
|
|
c.values["abc"] = "foo"
|
|
c.values["def"] = 123
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
assert.NotContains(t, c.values, "xyz")
|
|
|
|
// Values do not match skip condition. Prompt should not be skipped.
|
|
c.values["abc"] = "foobar"
|
|
c.values["def"] = 1234
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
assert.NotContains(t, c.values, "xyz")
|
|
|
|
// Values match skip condition. Prompt should be skipped. Default value should
|
|
// be assigned to "xyz".
|
|
c.values["abc"] = "foobar"
|
|
c.values["def"] = 123
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "hello-world", c.values["xyz"])
|
|
}
|
|
|
|
func TestPromptIsSkippedAnyOf(t *testing.T) {
|
|
c := config{
|
|
ctx: context.Background(),
|
|
values: make(map[string]any),
|
|
schema: &jsonschema.Schema{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Type: "string",
|
|
},
|
|
"def": {
|
|
Type: "integer",
|
|
},
|
|
"xyz": {
|
|
Type: "string",
|
|
Default: "hello-world",
|
|
Extension: jsonschema.Extension{
|
|
SkipPromptIf: &jsonschema.Schema{
|
|
AnyOf: []*jsonschema.Schema{
|
|
{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Const: "foobar",
|
|
},
|
|
"def": {
|
|
Const: 123,
|
|
},
|
|
},
|
|
Required: []string{"abc", "def"},
|
|
},
|
|
{
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"abc": {
|
|
Const: "barfoo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// No skip condition defined. Prompt should not be skipped.
|
|
skip, err := c.skipPrompt(jsonschema.Property{
|
|
Name: "abc",
|
|
Schema: c.schema.Properties["abc"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
|
|
// Values do not match skip condition. Prompt should not be skipped.
|
|
c.values = map[string]any{
|
|
"abc": "foobar",
|
|
"def": 1234,
|
|
}
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
assert.NotContains(t, c.values, "xyz")
|
|
|
|
// def is missing value. Prompt should not be skipped.
|
|
c.values = map[string]any{
|
|
"abc": "foobar",
|
|
}
|
|
_, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.False(t, skip)
|
|
assert.NotContains(t, c.values, "xyz")
|
|
|
|
// abc is missing value. Prompt should be skipped because abc is optional
|
|
// in second condition.
|
|
c.values = map[string]any{
|
|
"def": 123,
|
|
}
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "hello-world", c.values["xyz"])
|
|
|
|
// Values match skip condition. Prompt should be skipped. Default value should
|
|
// be assigned to "xyz".
|
|
c.values = map[string]any{
|
|
"abc": "foobar",
|
|
"def": 123,
|
|
}
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "hello-world", c.values["xyz"])
|
|
|
|
// Values match skip condition. Prompt should be skipped. Default value should
|
|
// be assigned to "xyz".
|
|
c.values = map[string]any{
|
|
"abc": "barfoo",
|
|
}
|
|
skip, err = c.skipPrompt(jsonschema.Property{
|
|
Name: "xyz",
|
|
Schema: c.schema.Properties["xyz"],
|
|
}, testRenderer())
|
|
assert.NoError(t, err)
|
|
assert.True(t, skip)
|
|
assert.Equal(t, "hello-world", c.values["xyz"])
|
|
}
|