databricks-cli/libs/template/config_test.go

564 lines
15 KiB
Go
Raw Normal View History

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{
2024-08-27 10:47:49 +00:00
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"])
}