From 0bfab18268aa0c26a70c76a3e136ce2c753fcb75 Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:45:59 +0100 Subject: [PATCH] Add acceptance tests for how provider handles `project` arguments (#11607) --- .../framework_provider_project_test.go | 194 ++++++++++++++++++ .../provider/provider_project_test.go | 193 +++++++++++++++++ 2 files changed, 387 insertions(+) create mode 100644 mmv1/third_party/terraform/fwprovider/framework_provider_project_test.go create mode 100644 mmv1/third_party/terraform/provider/provider_project_test.go diff --git a/mmv1/third_party/terraform/fwprovider/framework_provider_project_test.go b/mmv1/third_party/terraform/fwprovider/framework_provider_project_test.go new file mode 100644 index 000000000000..e7b41bcfc2cf --- /dev/null +++ b/mmv1/third_party/terraform/fwprovider/framework_provider_project_test.go @@ -0,0 +1,194 @@ +package fwprovider_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccFwProvider_project(t *testing.T) { + testCases := map[string]func(t *testing.T){ + // Configuring the provider using inputs + "config takes precedence over environment variables": testAccFwProvider_project_configPrecedenceOverEnvironmentVariables, + "when project is unset in the config, environment variables are used": testAccFwProvider_project_precedenceOrderEnvironmentVariables, + + // Schema-level validation + "when project is set to an empty string in the config the value isn't ignored": testAccFwProvider_project_emptyStringValidation, + + // Usage + // There aren't currently any test cases covering usage of the default project values here + // However use of default project values is present in the majority of our resource acceptance tests + } + + for name, tc := range testCases { + // shadow the tc variable into scope so that when + // the loop continues, if t.Run hasn't executed tc(t) + // yet, we don't have a race condition + // see https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccFwProvider_project_configPrecedenceOverEnvironmentVariables(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + project := envvar.GetTestProjectFromEnv() + + // set all possible project env vars to other value; show they aren't used + for _, v := range envvar.ProjectEnvVars { + t.Setenv(v, "foobar") + } + + context := map[string]interface{}{ + "project": project, + } + + acctest.VcrTest(t, resource.TestCase{ + // No PreCheck for checking ENVs + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccFwProvider_projectInProviderBlock(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "project", project), + ), + }, + }, + }) +} + +func testAccFwProvider_project_precedenceOrderEnvironmentVariables(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + /* + These are all the ENVs for project, and they are in order of precedence. + GOOGLE_PROJECT + GOOGLE_CLOUD_PROJECT + GCLOUD_PROJECT + CLOUDSDK_CORE_PROJECT + */ + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Unset all ENVs for project + PreConfig: func() { + for _, v := range envvar.ProjectEnvVars { + t.Setenv(v, "") + } + }, + Config: testAccFwProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + // Differing behavior between SDK and PF; the attribute is NOT found here. + // This reflects the different type systems used in the SDKv2 and the plugin-framework + resource.TestCheckNoResourceAttr("data.google_provider_config_plugin_framework.default", "project"), + ), + }, + { + // GOOGLE_PROJECT is used 1st if set + PreConfig: func() { + t.Setenv("GOOGLE_PROJECT", "GOOGLE_PROJECT") //used + t.Setenv("GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") + t.Setenv("GCLOUD_PROJECT", "GCLOUD_PROJECT") + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccFwProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "project", "GOOGLE_PROJECT"), + ), + }, + { + // GOOGLE_CLOUD_PROJECT is used 2nd if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + // set + t.Setenv("GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") // used + t.Setenv("GCLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccFwProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "project", "GOOGLE_CLOUD_PROJECT"), + ), + }, + { + // GCLOUD_PROJECT is used 3rd if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + t.Setenv("GOOGLE_CLOUD_PROJECT", "") + // set + t.Setenv("GCLOUD_PROJECT", "GCLOUD_PROJECT") //used + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccFwProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "project", "GCLOUD_PROJECT"), + ), + }, + { + // CLOUDSDK_CORE_PROJECT is used 4th if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + t.Setenv("GOOGLE_CLOUD_PROJECT", "") + t.Setenv("GCLOUD_PROJECT", "") + // set + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccFwProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "project", "CLOUDSDK_CORE_PROJECT"), + ), + }, + }, + }) +} + +func testAccFwProvider_project_emptyStringValidation(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + context := map[string]interface{}{ + "project": "", + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccFwProvider_projectInProviderBlock(context), + PlanOnly: true, + ExpectError: regexp.MustCompile("expected a non-empty string"), + }, + }, + }) +} + +// testAccFwProvider_projectInProviderBlock allows setting the project argument in a provider block. +func testAccFwProvider_projectInProviderBlock(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + project = "%{project}" +} + +data "google_provider_config_plugin_framework" "default" {} +`, context) +} + +// testAccFwProvider_projectInEnvsOnly allows testing when the project argument +// is only supplied via ENVs +func testAccFwProvider_projectInEnvsOnly() string { + return ` +data "google_provider_config_plugin_framework" "default" {} +` +} diff --git a/mmv1/third_party/terraform/provider/provider_project_test.go b/mmv1/third_party/terraform/provider/provider_project_test.go new file mode 100644 index 000000000000..913823be67a7 --- /dev/null +++ b/mmv1/third_party/terraform/provider/provider_project_test.go @@ -0,0 +1,193 @@ +package provider_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccSdkProvider_project(t *testing.T) { + testCases := map[string]func(t *testing.T){ + // Configuring the provider using inputs + "config takes precedence over environment variables": testAccSdkProvider_project_configPrecedenceOverEnvironmentVariables, + "when project is unset in the config, environment variables are used": testAccSdkProvider_project_precedenceOrderEnvironmentVariables, + + // Schema-level validation + "when project is set to an empty string in the config the value isn't ignored": testAccSdkProvider_project_emptyStringValidation, + + // Usage + // There aren't currently any test cases covering usage of the default project values here + // However use of default project values is present in the majority of our resource acceptance tests + } + + for name, tc := range testCases { + // shadow the tc variable into scope so that when + // the loop continues, if t.Run hasn't executed tc(t) + // yet, we don't have a race condition + // see https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccSdkProvider_project_configPrecedenceOverEnvironmentVariables(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + project := envvar.GetTestProjectFromEnv() + + // set all possible project env vars to other value; show they aren't used + for _, v := range envvar.ProjectEnvVars { + t.Setenv(v, "foobar") + } + + context := map[string]interface{}{ + "project": project, + } + + acctest.VcrTest(t, resource.TestCase{ + // No PreCheck for checking ENVs + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccSdkProvider_projectInProviderBlock(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", project), + ), + }, + }, + }) +} + +func testAccSdkProvider_project_precedenceOrderEnvironmentVariables(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + /* + These are all the ENVs for project, and they are in order of precedence. + GOOGLE_PROJECT + GOOGLE_CLOUD_PROJECT + GCLOUD_PROJECT + CLOUDSDK_CORE_PROJECT + */ + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Unset all ENVs for project + PreConfig: func() { + for _, v := range envvar.ProjectEnvVars { + t.Setenv(v, "") + } + }, + Config: testAccSdkProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + // Differing behavior between SDK and PF; the attribute is found here. + // This reflects the different type systems used in the SDKv2 and the plugin-framework + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", ""), + ), + }, + { + // GOOGLE_PROJECT is used 1st if set + PreConfig: func() { + t.Setenv("GOOGLE_PROJECT", "GOOGLE_PROJECT") // used + t.Setenv("GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") + t.Setenv("GCLOUD_PROJECT", "GCLOUD_PROJECT") + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccSdkProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", "GOOGLE_PROJECT"), + ), + }, + { + // GOOGLE_CLOUD_PROJECT is used 2nd if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + // set + t.Setenv("GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") //used + t.Setenv("GCLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT") + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccSdkProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", "GOOGLE_CLOUD_PROJECT"), + ), + }, + { + // GCLOUD_PROJECT is used 3rd if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + t.Setenv("GOOGLE_CLOUD_PROJECT", "") + // set + t.Setenv("GCLOUD_PROJECT", "GCLOUD_PROJECT") // used + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccSdkProvider_projectInEnvsOnly(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", "GCLOUD_PROJECT"), + ), + }, + { + // CLOUDSDK_CORE_PROJECT is used 4th if set + PreConfig: func() { + // unset + t.Setenv("GOOGLE_PROJECT", "") + t.Setenv("GOOGLE_CLOUD_PROJECT", "") + t.Setenv("GCLOUD_PROJECT", "") + // set + t.Setenv("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_CORE_PROJECT") + }, + Config: testAccSdkProvider_projectInEnvsOnly(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "project", "CLOUDSDK_CORE_PROJECT"), + ), + }, + }, + }) +} + +func testAccSdkProvider_project_emptyStringValidation(t *testing.T) { + acctest.SkipIfVcr(t) // Test doesn't interact with API + + context := map[string]interface{}{ + "project": "", + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccSdkProvider_projectInProviderBlock(context), + PlanOnly: true, + ExpectError: regexp.MustCompile("expected a non-empty string"), + }, + }, + }) +} + +// testAccSdkProvider_projectInProviderBlock allows setting the project argument in a provider block. +func testAccSdkProvider_projectInProviderBlock(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + project = "%{project}" +} +data "google_provider_config_sdk" "default" {} +`, context) +} + +// testAccSdkProvider_projectInEnvsOnly allows testing when the project argument +// is only supplied via ENVs +func testAccSdkProvider_projectInEnvsOnly() string { + return ` +data "google_provider_config_sdk" "default" {} +` +}