internal/acceptance/testcase.go (179 lines of code) (raw):

package acceptance import ( "fmt" "os" "testing" "github.com/Azure/terraform-provider-azapi/internal/azure/location" "github.com/Azure/terraform-provider-azapi/internal/clients" "github.com/Azure/terraform-provider-azapi/internal/provider" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) const ( // charSetAlphaNum is the alphanumeric character set for use with randStringFromCharSet charSetAlphaNum = "abcdefghijklmnopqrstuvwxyz012346789" ) type TestData struct { // LocationPrimary is the Primary Azure Region which should be used for testing LocationPrimary string // LocationSecondary is the Secondary Azure Region which should be used for testing LocationSecondary string // LocationTernary is the Ternary Azure Region which should be used for testing LocationTernary string // RandomInteger is a random integer which is unique to this test case RandomInteger int // RandomString is a random 5 character string is unique to this test case RandomString string // ResourceName is the fully qualified resource name, comprising of the // resource type and then the resource label // e.g. `azurerm_resource_group.test` ResourceName string // ResourceType is the Terraform Resource Type - `azurerm_resource_group` ResourceType string // resourceLabel is the local used for the resource - generally "test"" resourceLabel string } // BuildTestData generates some test data for the given resource func BuildTestData(t *testing.T, resourceType string, resourceLabel string) TestData { return TestData{ RandomInteger: RandTimeInt(), RandomString: acctest.RandStringFromCharSet(5, charSetAlphaNum), ResourceName: fmt.Sprintf("%s.%s", resourceType, resourceLabel), ResourceType: resourceType, resourceLabel: resourceLabel, LocationPrimary: location.Normalize(os.Getenv("ARM_TEST_LOCATION")), LocationSecondary: location.Normalize(os.Getenv("ARM_TEST_LOCATION_ALT")), LocationTernary: location.Normalize(os.Getenv("ARM_TEST_LOCATION_ALT2")), } } // RandomIntOfLength is a random 8 to 18 digit integer which is unique to this test case func (td *TestData) RandomInt() int { return RandTimeInt() } func (td *TestData) RandomStringOfLength(len int) string { return acctest.RandStringFromCharSet(len, charSetAlphaNum) } // UpgradeTestDeployStep returns a test step used to deploy the configuration with previous version func (td TestData) UpgradeTestDeployStep(step resource.TestStep, upgradeFrom string) resource.TestStep { if step.ExternalProviders == nil { step.ExternalProviders = td.externalProviders() } step.ExternalProviders["azapi"] = resource.ExternalProvider{ Source: "registry.terraform.io/azure/azapi", VersionConstraint: fmt.Sprintf("= %s", upgradeFrom), } step.ProtoV6ProviderFactories = nil return step } // UpgradeTestApplyStep returns a test step used to run terraform apply with the development version func (td TestData) UpgradeTestApplyStep(applyStep resource.TestStep) resource.TestStep { if applyStep.ExternalProviders == nil { applyStep.ExternalProviders = td.externalProviders() } applyStep.ProtoV6ProviderFactories = td.providers() return applyStep } // UpgradeTestPlanStep returns a test step used to run terraform plan with the development version to check if there's any changes func (td TestData) UpgradeTestPlanStep(planStep resource.TestStep) resource.TestStep { planStep.PlanOnly = true if planStep.ExternalProviders == nil { planStep.ExternalProviders = td.externalProviders() } planStep.ProtoV6ProviderFactories = td.providers() return planStep } func (td TestData) UpgradeTest(t *testing.T, testResource TestResource, steps []resource.TestStep) { testCase := resource.TestCase{ PreCheck: func() { PreCheck(t) }, CheckDestroy: func(s *terraform.State) error { client, err := BuildTestClient() if err != nil { return fmt.Errorf("building client: %+v", err) } return CheckDestroyedFunc(client, testResource, td.ResourceType, td.ResourceName)(s) }, Steps: steps, } resource.ParallelTest(t, testCase) } // lintignore:AT001 func (td TestData) DataSourceTest(t *testing.T, steps []resource.TestStep) { // DataSources don't need a check destroy - however since this is a wrapper function // and not matching the ignore pattern `XXX_data_source_test.go`, this needs to be explicitly opted out testCase := resource.TestCase{ PreCheck: func() { PreCheck(t) }, Steps: steps, } td.runAcceptanceTest(t, testCase) } func (td TestData) ResourceTest(t *testing.T, testResource TestResource, steps []resource.TestStep) { testCase := resource.TestCase{ PreCheck: func() { PreCheck(t) }, CheckDestroy: func(s *terraform.State) error { client, err := BuildTestClient() if err != nil { return fmt.Errorf("building client: %+v", err) } return CheckDestroyedFunc(client, testResource, td.ResourceType, td.ResourceName)(s) }, Steps: steps, } td.runAcceptanceTest(t, testCase) } func (td TestData) runAcceptanceTest(t *testing.T, testCase resource.TestCase) { testCase.ExternalProviders = td.externalProviders() // If any test steps require their own external providers, then we need to clear the global list providersInTestStep := false for i, step := range testCase.Steps { if step.ExternalProviders != nil { testCase.ExternalProviders = nil step.ProtoV6ProviderFactories = td.providers() testCase.Steps[i] = step providersInTestStep = true } } if !providersInTestStep { testCase.ProtoV6ProviderFactories = td.providers() } resource.ParallelTest(t, testCase) } func (td TestData) providers() map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ "azapi": providerserver.NewProtocol6WithError(provider.AzureProvider()), } } func (td TestData) externalProviders() map[string]resource.ExternalProvider { return map[string]resource.ExternalProvider{} } func PreCheck(t *testing.T) { if v := os.Getenv("TF_ACC"); v == "" { t.Fatalf(`TF_ACC must be set for acceptance tests! For tests that authenticate with Azure by using a Service Principal, the following environment variables must be set: - ARM_CLIENT_ID - ARM_CLIENT_SECRET - ARM_SUBSCRIPTION_ID - ARM_TENANT_ID - ARM_TEST_LOCATION - ARM_TEST_LOCATION_ALT - ARM_TEST_LOCATION_ALT2 For tests that authenticate with Azure by OIDC in github action, the following environment variables must be set: - ARM_CLIENT_ID - ARM_TENANT_ID - ARM_TEST_LOCATION - ARM_TEST_LOCATION_ALT - ARM_TEST_LOCATION_ALT2 For tests that authenticate with Azure by using a Service Principal with Certificate, the following environment variables must be set: - ARM_CLIENT_ID - ARM_CLIENT_CERTIFICATE_PATH - ARM_SUBSCRIPTION_ID - ARM_TENANT_ID - ARM_TEST_LOCATION - ARM_TEST_LOCATION_ALT - ARM_TEST_LOCATION_ALT2 `) } } // CheckDestroyedFunc returns a TestCheckFunc which validates the resource no longer exists func CheckDestroyedFunc(client *clients.Client, testResource TestResource, resourceType, resourceName string) func(state *terraform.State) error { return func(state *terraform.State) error { ctx := client.StopContext for label, resourceState := range state.RootModule().Resources { if resourceState.Type != resourceType { continue } if label != resourceName { continue } // Destroy is unconcerned with an error checking the status, since this is going to be "not found" result, err := testResource.Exists(ctx, client, resourceState.Primary) if result == nil && err == nil { return fmt.Errorf("should have either an error or a result when checking if %q has been destroyed", resourceName) } if result != nil && *result { return fmt.Errorf("%q still exists", resourceName) } } return nil } }