Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ability to import the CLI repository as module #1671

Merged
merged 5 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions internal/testutil/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package testutil

import (
"io"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func CopyDirectory(t *testing.T, src, dst string) {
err := filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

rel, err := filepath.Rel(src, path)
require.NoError(t, err)

if d.IsDir() {
return os.MkdirAll(filepath.Join(dst, rel), 0755)
}

// Copy the file to the temporary directory
in, err := os.Open(path)
if err != nil {
return err
}

defer in.Close()

out, err := os.Create(filepath.Join(dst, rel))
if err != nil {
return err
}

defer out.Close()

_, err = io.Copy(out, in)
return err
})

require.NoError(t, err)
}
151 changes: 92 additions & 59 deletions libs/template/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,110 +3,153 @@ package template
import (
"context"
"fmt"
"path/filepath"
"testing"
"text/template"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/jsonschema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func testConfig(t *testing.T) *config {
c, err := newConfig(context.Background(), "./testdata/config-test-schema/test-schema.json")
require.NoError(t, err)
return c
}

func TestTemplateConfigAssignValuesFromFile(t *testing.T) {
c := testConfig(t)
testDir := "./testdata/config-assign-from-file"

err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
assert.NoError(t, err)
ctx := context.Background()
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
require.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"])
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 TestTemplateConfigAssignValuesFromFileForInvalidIntegerValue(t *testing.T) {
c := testConfig(t)
func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *testing.T) {
testDir := "./testdata/config-assign-from-file"

err := c.assignValuesFromFile("./testdata/config-assign-from-file-invalid-int/config.json")
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")
}
ctx := context.Background()
c, err := newConfig(ctx, filepath.Join(testDir, "schema.json"))
require.NoError(t, err)

func TestTemplateConfigAssignValuesFromFileDoesNotOverwriteExistingConfigs(t *testing.T) {
c := testConfig(t)
c.values = map[string]any{
"string_val": "this-is-not-overwritten",
}

err := c.assignValuesFromFile("./testdata/config-assign-from-file/config.json")
assert.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, "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)

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"])
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) {
c := testConfig(t)
testDir := "./testdata/config-assign-from-file-unknown-property"

err := c.assignValuesFromFile("./testdata/config-assign-from-file-unknown-property/config.json")
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 TestTemplateConfigAssignDefaultValues(t *testing.T) {
c := testConfig(t)
func TestTemplateConfigAssignValuesFromDefaultValues(t *testing.T) {
testDir := "./testdata/config-assign-from-default-value"

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())
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)
assert.NoError(t, err)
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)

assert.Len(t, c.values, 2)
assert.Equal(t, "my_file", c.values["string_val"])
assert.Equal(t, int64(123), c.values["int_val"])
// 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) {
c := testConfig(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()
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) {
c := testConfig(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()
err = c.validate()
assert.NoError(t, err)
}

func TestTemplateConfigValidateTypeForUnknownField(t *testing.T) {
c := testConfig(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,
Expand All @@ -115,20 +158,23 @@ func TestTemplateConfigValidateTypeForUnknownField(t *testing.T) {
"string_val": "abcd",
}

err := c.validate()
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) {
c := testConfig(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()
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\"")
}

Expand Down Expand Up @@ -224,19 +270,6 @@ func TestTemplateEnumValidation(t *testing.T) {
assert.NoError(t, c.validate())
}

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"])
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test now uses its own configuration schema and template.

See TestTemplateConfigAssignValuesFromTemplatedDefaultValues.

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")
Expand Down
20 changes: 15 additions & 5 deletions libs/template/renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
bundleConfig "github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/internal/testutil"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/tags"
"github.com/databricks/databricks-sdk-go"
Expand Down Expand Up @@ -655,15 +656,24 @@ func TestRendererFileTreeRendering(t *testing.T) {
func TestRendererSubTemplateInPath(t *testing.T) {
ctx := context.Background()
ctx = root.SetWorkspaceClient(ctx, nil)
tmpDir := t.TempDir()

helpers := loadHelpers(ctx)
r, err := newRenderer(ctx, nil, helpers, "./testdata/template-in-path/template", "./testdata/template-in-path/library", tmpDir)
// Copy the template directory to a temporary directory where we can safely include a templated file path.
// These paths include characters that are forbidden in Go modules, so we can't use the testdata directory.
// Also see https://github.com/databricks/cli/pull/1671.
templateDir := t.TempDir()
testutil.CopyDirectory(t, "./testdata/template-in-path", templateDir)
testutil.Touch(t, filepath.Join(templateDir, `template/{{template "dir_name"}}/{{template "file_name"}}`))

tmpDir := t.TempDir()
r, err := newRenderer(ctx, nil, nil, filepath.Join(templateDir, "template"), filepath.Join(templateDir, "library"), tmpDir)
require.NoError(t, err)

err = r.walk()
require.NoError(t, err)

assert.Equal(t, filepath.Join(tmpDir, "my_directory", "my_file"), r.files[0].DstPath().absPath())
assert.Equal(t, "my_directory/my_file", r.files[0].DstPath().relPath)
if assert.Len(t, r.files, 2) {
f := r.files[1]
assert.Equal(t, filepath.Join(tmpDir, "my_directory", "my_file"), f.DstPath().absPath())
assert.Equal(t, "my_directory/my_file", f.DstPath().relPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"properties": {
"int_val": {
"type": "integer",
"description": "This is an integer value",
"default": 123
},
"float_val": {
"type": "number",
"description": "This is a float value",
"default": 123
},
"bool_val": {
"type": "boolean",
"description": "This is a boolean value",
"default": true
},
"string_val": {
"type": "string",
"description": "This is a string value",
"default": "hello"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"properties": {
"int_val": {
"type": "integer",
"description": "This is an integer value"
},
"float_val": {
"type": "number",
"description": "This is a float value"
},
"bool_val": {
"type": "boolean",
"description": "This is a boolean value"
},
"string_val": {
"type": "string",
"description": "This is a string value"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"properties": {
"int_val": {
"type": "integer",
"description": "This is an integer value"
},
"float_val": {
"type": "number",
"description": "This is a float value"
},
"bool_val": {
"type": "boolean",
"description": "This is a boolean value"
},
"string_val": {
"type": "string",
"description": "This is a string value"
}
}
}
Loading
Loading