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
}
}