2023-08-07 13:14:25 +00:00
package template
import (
2023-09-07 14:36:06 +00:00
"context"
2023-11-30 14:28:51 +00:00
"fmt"
2023-08-07 13:14:25 +00:00
"testing"
2023-11-30 16:07:45 +00:00
"text/template"
2023-08-07 13:14:25 +00:00
2023-10-19 07:08:36 +00:00
"github.com/databricks/cli/cmd/root"
2023-08-07 13:14:25 +00:00
"github.com/databricks/cli/libs/jsonschema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
2023-09-07 14:36:06 +00:00
func testConfig ( t * testing . T ) * config {
c , err := newConfig ( context . Background ( ) , "./testdata/config-test-schema/test-schema.json" )
2023-08-07 13:14:25 +00:00
require . NoError ( t , err )
2023-09-07 14:36:06 +00:00
return c
2023-08-07 13:14:25 +00:00
}
func TestTemplateConfigAssignValuesFromFile ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
2023-08-07 13:14:25 +00:00
err := c . assignValuesFromFile ( "./testdata/config-assign-from-file/config.json" )
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 TestTemplateConfigAssignValuesFromFileForInvalidIntegerValue ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
2023-08-07 13:14:25 +00:00
err := c . assignValuesFromFile ( "./testdata/config-assign-from-file-invalid-int/config.json" )
2023-09-07 14:36:06 +00:00
assert . EqualError ( t , err , "failed to load config from file ./testdata/config-assign-from-file-invalid-int/config.json: failed to parse property int_val: cannot convert \"abc\" to an integer" )
2023-08-07 13:14:25 +00:00
}
func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
c . values = map [ string ] any {
"string_val" : "this-is-not-overwritten" ,
2023-08-07 13:14:25 +00:00
}
err := c . assignValuesFromFile ( "./testdata/config-assign-from-file/config.json" )
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 TestTemplateConfigAssignDefaultValues ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
2023-08-07 13:14:25 +00:00
2023-10-19 07:08:36 +00:00
ctx := context . Background ( )
ctx = root . SetWorkspaceClient ( ctx , nil )
helpers := loadHelpers ( ctx )
r , err := newRenderer ( ctx , nil , helpers , "./testdata/template-in-path/template" , "./testdata/template-in-path/library" , t . TempDir ( ) )
require . NoError ( t , err )
err = c . assignDefaultValues ( r )
2023-08-07 13:14:25 +00:00
assert . NoError ( t , err )
assert . Len ( t , c . values , 2 )
2023-10-19 07:08:36 +00:00
assert . Equal ( t , "my_file" , c . values [ "string_val" ] )
2023-08-07 13:14:25 +00:00
assert . Equal ( t , int64 ( 123 ) , c . values [ "int_val" ] )
}
func TestTemplateConfigValidateValuesDefined ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
c . values = map [ string ] any {
"int_val" : 1 ,
"float_val" : 1.0 ,
"bool_val" : false ,
2023-08-07 13:14:25 +00:00
}
2023-09-07 14:36:06 +00:00
err := c . validate ( )
assert . EqualError ( t , err , "validation for template input parameters failed. no value provided for required property string_val" )
2023-08-07 13:14:25 +00:00
}
func TestTemplateConfigValidateTypeForValidConfig ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
c . values = map [ string ] any {
"int_val" : 1 ,
"float_val" : 1.1 ,
"bool_val" : true ,
"string_val" : "abcd" ,
2023-08-07 13:14:25 +00:00
}
2023-09-07 14:36:06 +00:00
err := c . validate ( )
2023-08-07 13:14:25 +00:00
assert . NoError ( t , err )
}
func TestTemplateConfigValidateTypeForUnknownField ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
c . values = map [ string ] any {
"unknown_prop" : 1 ,
"int_val" : 1 ,
"float_val" : 1.1 ,
"bool_val" : true ,
"string_val" : "abcd" ,
2023-08-07 13:14:25 +00:00
}
2023-09-07 14:36:06 +00:00
err := c . validate ( )
assert . EqualError ( t , err , "validation for template input parameters failed. property unknown_prop is not defined in the schema" )
2023-08-07 13:14:25 +00:00
}
func TestTemplateConfigValidateTypeForInvalidType ( t * testing . T ) {
2023-09-07 14:36:06 +00:00
c := testConfig ( t )
c . values = map [ string ] any {
"int_val" : "this-should-be-an-int" ,
"float_val" : 1.1 ,
"bool_val" : true ,
"string_val" : "abcd" ,
2023-08-07 13:14:25 +00:00
}
2023-09-07 14:36:06 +00:00
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\"" )
2023-08-07 13:14:25 +00:00
}
2023-08-15 14:28:04 +00:00
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" )
}
2023-09-08 12:07:22 +00:00
2023-11-30 14:28:51 +00:00
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 ) )
}
2023-09-08 12:07:22 +00:00
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 ( ) )
}
2023-10-19 07:08:36 +00:00
func TestAssignDefaultValuesWithTemplatedDefaults ( t * testing . T ) {
c := testConfig ( t )
ctx := context . Background ( )
ctx = root . SetWorkspaceClient ( ctx , nil )
helpers := loadHelpers ( ctx )
r , err := newRenderer ( ctx , nil , helpers , "./testdata/templated-defaults/template" , "./testdata/templated-defaults/library" , t . TempDir ( ) )
require . NoError ( t , err )
err = c . assignDefaultValues ( r )
assert . NoError ( t , err )
assert . Equal ( t , "my_file" , c . values [ "string_val" ] )
}
2023-11-08 16:48:37 +00:00
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" )
}
2023-11-30 16:07:45 +00:00
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 ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
}
// 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" ] )
}
2024-01-19 09:21:24 +00:00
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 ,
} ,
} ,
} ,
{
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.
2024-01-24 06:40:55 +00:00
c . values = map [ string ] any {
"abc" : "foobar" ,
"def" : 1234 ,
}
2024-01-19 09:21:24 +00:00
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" )
2024-01-22 06:52:31 +00:00
// Missing values. Prompt should not be skipped.
2024-01-24 06:40:55 +00:00
c . values = map [ string ] any {
"abc" : "foobar" ,
}
_ , err = c . skipPrompt ( jsonschema . Property {
2024-01-22 06:52:31 +00:00
Name : "xyz" ,
Schema : c . schema . Properties [ "xyz" ] ,
} , testRenderer ( ) )
2024-01-24 06:40:55 +00:00
assert . ErrorContains ( t , err , "property def is used in skip_prompt_if but has no value assigned" )
2024-01-22 06:52:31 +00:00
2024-01-19 09:21:24 +00:00
// Values match skip condition. Prompt should be skipped. Default value should
// be assigned to "xyz".
2024-01-24 06:40:55 +00:00
c . values = map [ string ] any {
"abc" : "foobar" ,
"def" : 123 ,
}
2024-01-19 09:21:24 +00:00
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".
2024-01-24 06:40:55 +00:00
c . values = map [ string ] any {
"abc" : "barfoo" ,
"def" : 0 ,
}
2024-01-19 09:21:24 +00:00
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" ] )
}