diff --git a/azurerm/internal/services/databricks/databricks_workspace_resource.go b/azurerm/internal/services/databricks/databricks_workspace_resource.go index 62d211bfdcc0..1b626c115780 100644 --- a/azurerm/internal/services/databricks/databricks_workspace_resource.go +++ b/azurerm/internal/services/databricks/databricks_workspace_resource.go @@ -355,23 +355,29 @@ func ValidateDatabricksWorkspaceName(i interface{}, k string) (warnings []string return warnings, errors } - // Cannot be empty + // The Azure Portal shows the following validation criteria: + + // 1) Cannot be empty if len(v) == 0 { errors = append(errors, fmt.Errorf("%q cannot be an empty string: %q", k, v)) + // Treating this as a special case and returning early to match Azure Portal behavior. return warnings, errors } - // First, second, and last characters must be a letter or number with a total length between 3 to 64 characters + // 2) Must be at least 3 characters: + if len(v) < 3 { + errors = append(errors, fmt.Errorf("%q must be at least 3 characters: %q", k, v)) + } + + // 3) The value must have a length of at most 30. // NOTE: Restricted name to 30 characters because that is the restriction in Azure Portal even though the API supports 64 characters - if !regexp.MustCompile("^[a-zA-Z0-9]{2}[-_a-zA-Z0-9]{0,27}[a-zA-Z0-9]{1}$").MatchString(v) { - errors = append(errors, fmt.Errorf("%q must be 3 - 30 characters in length", k)) - errors = append(errors, fmt.Errorf("%q first, second, and last characters must be a letter or number", k)) - errors = append(errors, fmt.Errorf("%q can only contain letters, numbers, underscores, and hyphens", k)) + if len(v) > 30 { + errors = append(errors, fmt.Errorf("%q must be no more than 30 characters: %q", k, v)) } - // No consecutive hyphens - if regexp.MustCompile("(--)").MatchString(v) { - errors = append(errors, fmt.Errorf("%q must not contain any consecutive hyphens", k)) + // 4) Only alphanumeric characters, underscores, and hyphens are allowed. + if !regexp.MustCompile("^[a-zA-Z0-9_-]*$").MatchString(v) { + errors = append(errors, fmt.Errorf("%q can contain only alphanumeric characters, underscores, and hyphens: %q", k, v)) } return warnings, errors diff --git a/azurerm/internal/services/databricks/tests/databricks_workspace_resource_test.go b/azurerm/internal/services/databricks/tests/databricks_workspace_resource_test.go index cbf7b7914142..3e39a1ded1bb 100644 --- a/azurerm/internal/services/databricks/tests/databricks_workspace_resource_test.go +++ b/azurerm/internal/services/databricks/tests/databricks_workspace_resource_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "regexp" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -15,71 +16,89 @@ import ( ) func TestAzureRMDatabrickWorkspaceName(t *testing.T) { + const errEmpty = "cannot be an empty string" + const errMinLen = "must be at least 3 characters" + const errMaxLen = "must be no more than 30 characters" + const errAllowList = "can contain only alphanumeric characters, underscores, and hyphens" + cases := []struct { - Value string - ShouldError bool + Name string + Input string + ExpectedErrors []string }{ + // Happy paths: { - Value: "hello", - ShouldError: false, - }, - { - Value: "hello123there", - ShouldError: false, - }, - { - Value: "hello-1-2-3-there", - ShouldError: false, - }, - { - Value: "hello_1_2_3_there", - ShouldError: false, + Name: "Entire character allow-list", + Input: "aZ09_-", }, { - Value: "hello-1-2-3-", - ShouldError: true, + Name: "Minimum character length", + Input: "---", }, { - Value: "-hello-1-2-3", - ShouldError: true, + Name: "Maximum character length", + Input: "012345678901234567890123456789", // 30 chars }, + + // Simple negative cases: { - Value: "hello_1_2_3_", - ShouldError: true, + Name: "Introduce a non-allowed character", + Input: "aZ09_-$", // dollar sign + ExpectedErrors: []string{errAllowList}, }, { - Value: "_hello_1_2_3", - ShouldError: true, + Name: "Below minimum character length", + Input: "--", + ExpectedErrors: []string{errMinLen}, }, { - Value: "hello!there", - ShouldError: true, + Name: "Above maximum character length", + Input: "0123456789012345678901234567890", // 31 chars + ExpectedErrors: []string{errMaxLen}, }, { - Value: "hello--there", - ShouldError: true, + Name: "Specifically test for emptiness", + Input: "", + ExpectedErrors: []string{errEmpty}, }, + + // Complex negative cases { - Value: "!hellothere", - ShouldError: true, + Name: "Too short and non-allowed char", + Input: "*^", + ExpectedErrors: []string{errMinLen, errAllowList}, }, { - Value: "hellothere!", - ShouldError: true, + Name: "Too long and non-allowed char", + Input: "012345678901234567890123456789ß", + ExpectedErrors: []string{errMaxLen, errAllowList}, }, } - for _, tc := range cases { - _, errors := databricks.ValidateDatabricksWorkspaceName(tc.Value, "test") - - hasErrors := len(errors) > 0 - if hasErrors && !tc.ShouldError { - t.Fatalf("Expected no errors but got %d for %q", len(errors), tc.Value) + errsContain := func(errors []error, text string) bool { + for _, err := range errors { + if strings.Contains(err.Error(), text) { + return true + } } + return false + } - if !hasErrors && tc.ShouldError { - t.Fatalf("Expected no errors but got %d for %q", len(errors), tc.Value) - } + t.Parallel() + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + _, errors := databricks.ValidateDatabricksWorkspaceName(tc.Input, "azurerm_databricks_workspace.test.name") + + if len(errors) != len(tc.ExpectedErrors) { + t.Fatalf("Expected %d errors but got %d for %q: %v", len(tc.ExpectedErrors), len(errors), tc.Input, errors) + } + + for _, expectedError := range tc.ExpectedErrors { + if !errsContain(errors, expectedError) { + t.Fatalf("Errors did not contain expected error: %s", expectedError) + } + } + }) } }