package render import ( "bytes" "testing" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" assert "github.com/databricks/cli/libs/dyn/dynassert" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/require" ) type renderTestOutputTestCase struct { name string bundle *bundle.Bundle diags diag.Diagnostics opts RenderOptions expected string } func TestRenderTextOutput(t *testing.T) { loadingBundle := &bundle.Bundle{ Config: config.Root{ Bundle: config.Bundle{ Name: "test-bundle", Target: "test-target", }, }, } testCases := []renderTestOutputTestCase{ { name: "nil bundle and 1 error", diags: diag.Diagnostics{ { Severity: diag.Error, Summary: "failed to load xxx", }, }, opts: RenderOptions{RenderSummaryTable: true}, expected: "Error: failed to load xxx\n" + "\n" + "Found 1 error\n", }, { name: "bundle during 'load' and 1 error", bundle: loadingBundle, diags: diag.Errorf("failed to load bundle"), opts: RenderOptions{RenderSummaryTable: true}, expected: "Error: failed to load bundle\n" + "\n" + "Name: test-bundle\n" + "Target: test-target\n" + "\n" + "Found 1 error\n", }, { name: "bundle during 'load' and 1 warning", bundle: loadingBundle, diags: diag.Warningf("failed to load bundle"), opts: RenderOptions{RenderSummaryTable: true}, expected: "Warning: failed to load bundle\n" + "\n" + "Name: test-bundle\n" + "Target: test-target\n" + "\n" + "Found 1 warning\n", }, { name: "bundle during 'load' and 2 warnings", bundle: loadingBundle, diags: diag.Warningf("warning (1)").Extend(diag.Warningf("warning (2)")), opts: RenderOptions{RenderSummaryTable: true}, expected: "Warning: warning (1)\n" + "\n" + "Warning: warning (2)\n" + "\n" + "Name: test-bundle\n" + "Target: test-target\n" + "\n" + "Found 2 warnings\n", }, { name: "bundle during 'load' and 2 errors, 1 warning with details", bundle: loadingBundle, diags: diag.Diagnostics{ diag.Diagnostic{ Severity: diag.Error, Summary: "error (1)", Detail: "detail (1)", Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}}, }, diag.Diagnostic{ Severity: diag.Error, Summary: "error (2)", Detail: "detail (2)", Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}}, }, diag.Diagnostic{ Severity: diag.Warning, Summary: "warning (3)", Detail: "detail (3)", Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}}, }, }, opts: RenderOptions{RenderSummaryTable: true}, expected: "Error: error (1)\n" + " in foo.py:1:1\n" + "\n" + "detail (1)\n" + "\n" + "Error: error (2)\n" + " in foo.py:2:1\n" + "\n" + "detail (2)\n" + "\n" + "Warning: warning (3)\n" + " in foo.py:3:1\n" + "\n" + "detail (3)\n" + "\n" + "Name: test-bundle\n" + "Target: test-target\n" + "\n" + "Found 2 errors and 1 warning\n", }, { name: "bundle during 'init'", bundle: &bundle.Bundle{ Config: config.Root{ Bundle: config.Bundle{ Name: "test-bundle", Target: "test-target", }, Workspace: config.Workspace{ Host: "https://localhost/", CurrentUser: &config.User{ User: &iam.User{ UserName: "test-user", }, }, RootPath: "/Users/test-user@databricks.com/.bundle/examples/test-target", }, }, }, diags: nil, opts: RenderOptions{RenderSummaryTable: true}, expected: "Name: test-bundle\n" + "Target: test-target\n" + "Workspace:\n" + " Host: https://localhost/\n" + " User: test-user\n" + " Path: /Users/test-user@databricks.com/.bundle/examples/test-target\n" + "\n" + "Validation OK!\n", }, { name: "nil bundle without summary with 1 error and 1 warning", bundle: nil, diags: diag.Diagnostics{ diag.Diagnostic{ Severity: diag.Error, Summary: "error (1)", Detail: "detail (1)", Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}}, }, diag.Diagnostic{ Severity: diag.Warning, Summary: "warning (2)", Detail: "detail (2)", Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}}, }, }, opts: RenderOptions{RenderSummaryTable: false}, expected: "Error: error (1)\n" + " in foo.py:1:1\n" + "\n" + "detail (1)\n" + "\n" + "Warning: warning (2)\n" + " in foo.py:3:1\n" + "\n" + "detail (2)\n" + "\n", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { writer := &bytes.Buffer{} err := RenderTextOutput(writer, tc.bundle, tc.diags, tc.opts) require.NoError(t, err) assert.Equal(t, tc.expected, writer.String()) }) } } type renderDiagnosticsTestCase struct { name string diags diag.Diagnostics expected string } func TestRenderDiagnostics(t *testing.T) { bundle := &bundle.Bundle{} testCases := []renderDiagnosticsTestCase{ { name: "empty diagnostics", diags: diag.Diagnostics{}, expected: "", }, { name: "error with short summary", diags: diag.Diagnostics{ { Severity: diag.Error, Summary: "failed to load xxx", }, }, expected: "Error: failed to load xxx\n\n", }, { name: "error with source location", diags: diag.Diagnostics{ { Severity: diag.Error, Summary: "failed to load xxx", Detail: "'name' is required", Locations: []dyn.Location{{ File: "foo.yaml", Line: 1, Column: 2}}, }, }, expected: "Error: failed to load xxx\n" + " in foo.yaml:1:2\n\n" + "'name' is required\n\n", }, { name: "error with multiple source locations", diags: diag.Diagnostics{ { Severity: diag.Error, Summary: "failed to load xxx", Detail: "'name' is required", Locations: []dyn.Location{ { File: "foo.yaml", Line: 1, Column: 2, }, { File: "bar.yaml", Line: 3, Column: 4, }, }, }, }, expected: "Error: failed to load xxx\n" + " in foo.yaml:1:2\n" + " bar.yaml:3:4\n\n" + "'name' is required\n\n", }, { name: "error with path", diags: diag.Diagnostics{ { Severity: diag.Error, Detail: "'name' is required", Summary: "failed to load xxx", Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.xxx")}, }, }, expected: "Error: failed to load xxx\n" + " at resources.jobs.xxx\n" + "\n" + "'name' is required\n\n", }, { name: "error with multiple paths", diags: diag.Diagnostics{ { Severity: diag.Error, Detail: "'name' is required", Summary: "failed to load xxx", Paths: []dyn.Path{ dyn.MustPathFromString("resources.jobs.xxx"), dyn.MustPathFromString("resources.jobs.yyy"), dyn.MustPathFromString("resources.jobs.zzz"), }, }, }, expected: "Error: failed to load xxx\n" + " at resources.jobs.xxx\n" + " resources.jobs.yyy\n" + " resources.jobs.zzz\n" + "\n" + "'name' is required\n\n", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { writer := &bytes.Buffer{} err := renderDiagnostics(writer, bundle, tc.diags) require.NoError(t, err) assert.Equal(t, tc.expected, writer.String()) }) } } func TestRenderSummaryTemplate_nilBundle(t *testing.T) { writer := &bytes.Buffer{} err := renderSummaryTemplate(writer, nil, nil) require.NoError(t, err) assert.Equal(t, "Validation OK!\n", writer.String()) }