From 2e6c321b02a7b5bb0c8f1e941189085ee7320a12 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 09:10:44 -0400 Subject: [PATCH 01/19] Prepare to move test to a directory --- .../testing_new_import_block_id_test.go | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/helper/resource/testing_new_import_block_id_test.go b/helper/resource/testing_new_import_block_id_test.go index 51219b77b..36254f86c 100644 --- a/helper/resource/testing_new_import_block_id_test.go +++ b/helper/resource/testing_new_import_block_id_test.go @@ -1,15 +1,16 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package resource +package resource_test import ( "fmt" - "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" - "github.com/hashicorp/terraform-plugin-testing/terraform" "regexp" "testing" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -17,14 +18,16 @@ import ( "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestTest_TestStep_ImportBlockId(t *testing.T) { t.Parallel() - UnitTest(t, TestCase{ + r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -105,7 +108,7 @@ func TestTest_TestStep_ImportBlockId(t *testing.T) { }, }), }, - Steps: []TestStep{ + Steps: []r.TestStep{ { Config: ` resource "examplecloud_container" "test" { @@ -116,7 +119,7 @@ func TestTest_TestStep_ImportBlockId(t *testing.T) { { ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: ImportBlockWithId, + ImportStateKind: r.ImportBlockWithId, ImportStateVerify: true, }, }, @@ -126,9 +129,9 @@ func TestTest_TestStep_ImportBlockId(t *testing.T) { func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { t.Parallel() - UnitTest(t, TestCase{ + r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -209,7 +212,7 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { }, }), }, - Steps: []TestStep{ + Steps: []r.TestStep{ { Config: ` resource "examplecloud_container" "test" { @@ -225,7 +228,7 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { }`, ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: ImportBlockWithId, + ImportStateKind: r.ImportBlockWithId, ImportStateVerify: true, ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test should be a no-op, but got action update with plan(.?)`), }, @@ -236,9 +239,10 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { t.Parallel() - UnitTest(t, TestCase{ + r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -314,7 +318,7 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { }, }), }, - Steps: []TestStep{ + Steps: []r.TestStep{ { Config: ` data "examplecloud_thing" "test" {} @@ -324,7 +328,7 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { { ResourceName: "examplecloud_thing.test", ImportState: true, - ImportStateKind: ImportBlockWithId, + ImportStateKind: r.ImportBlockWithId, ImportStateCheck: func(is []*terraform.InstanceState) error { if len(is) > 1 { return fmt.Errorf("expected 1 state, got: %d", len(is)) @@ -365,9 +369,9 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes */ t.Parallel() - UnitTest(t, TestCase{ + r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -432,7 +436,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes }, }), }, - Steps: []TestStep{ + Steps: []r.TestStep{ { Config: ` resource "examplecloud_container" "test" { @@ -455,7 +459,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes }`, ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: ImportBlockWithId, + ImportStateKind: r.ImportBlockWithId, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"password"}, }, @@ -466,9 +470,9 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) { t.Parallel() - UnitTest(t, TestCase{ + r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -533,14 +537,14 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) { }, }), }, - Steps: []TestStep{ + Steps: []r.TestStep{ { Config: `resource "examplecloud_container" "test" {}`, }, { ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: ImportBlockWithId, + ImportStateKind: r.ImportBlockWithId, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"password"}, }, From 288ec269bc257d413bd363b9b306fc644a83a57a Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 09:14:12 -0400 Subject: [PATCH 02/19] Move test to import_test package --- .../import_block_with_id_test.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename helper/resource/{testing_new_import_block_id_test.go => import/import_block_with_id_test.go} (99%) diff --git a/helper/resource/testing_new_import_block_id_test.go b/helper/resource/import/import_block_with_id_test.go similarity index 99% rename from helper/resource/testing_new_import_block_id_test.go rename to helper/resource/import/import_block_with_id_test.go index 36254f86c..46bfb7077 100644 --- a/helper/resource/testing_new_import_block_id_test.go +++ b/helper/resource/import/import_block_with_id_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package resource_test +package import_test import ( "fmt" From e9247151504d34baf474b79ed8ce73107299e37b Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 09:24:50 -0400 Subject: [PATCH 03/19] Extract testprovider resource from test --- .../resource/importstate/examplecloud_test.go | 84 ++++++++++ .../import_block_with_id_test.go | 150 +----------------- 2 files changed, 87 insertions(+), 147 deletions(-) create mode 100644 helper/resource/importstate/examplecloud_test.go rename helper/resource/{import => importstate}/import_block_with_id_test.go (73%) diff --git a/helper/resource/importstate/examplecloud_test.go b/helper/resource/importstate/examplecloud_test.go new file mode 100644 index 000000000..043645daf --- /dev/null +++ b/helper/resource/importstate/examplecloud_test.go @@ -0,0 +1,84 @@ +package importstate_test + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" +) + +func examplecloudResource() testprovider.Resource { + return testprovider.Resource{ + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ReadResponse: &resource.ReadResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "location", + Type: tftypes.String, + Required: true, + }, + { + Name: "name", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + } +} diff --git a/helper/resource/import/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go similarity index 73% rename from helper/resource/import/import_block_with_id_test.go rename to helper/resource/importstate/import_block_with_id_test.go index 46bfb7077..0339ae57b 100644 --- a/helper/resource/import/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package import_test +package importstate_test import ( "fmt" @@ -32,79 +32,7 @@ func TestTest_TestStep_ImportBlockId(t *testing.T) { ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ - "examplecloud_container": { - CreateResponse: &resource.CreateResponse{ - NewState: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - ReadResponse: &resource.ReadResponse{ - NewState: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - ImportStateResponse: &resource.ImportStateResponse{ - State: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - SchemaResponse: &resource.SchemaResponse{ - Schema: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "id", - Type: tftypes.String, - Computed: true, - }, - { - Name: "location", - Type: tftypes.String, - Required: true, - }, - { - Name: "name", - Type: tftypes.String, - Required: true, - }, - }, - }, - }, - }, - }, + "examplecloud_container": examplecloudResource(), }, }), }, @@ -136,79 +64,7 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ - "examplecloud_container": { - CreateResponse: &resource.CreateResponse{ - NewState: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - ReadResponse: &resource.ReadResponse{ - NewState: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - ImportStateResponse: &resource.ImportStateResponse{ - State: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "location": tftypes.String, - "name": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), - "location": tftypes.NewValue(tftypes.String, "westeurope"), - "name": tftypes.NewValue(tftypes.String, "somevalue"), - }, - ), - }, - SchemaResponse: &resource.SchemaResponse{ - Schema: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "id", - Type: tftypes.String, - Computed: true, - }, - { - Name: "location", - Type: tftypes.String, - Required: true, - }, - { - Name: "name", - Type: tftypes.String, - Required: true, - }, - }, - }, - }, - }, - }, + "examplecloud_container": examplecloudResource(), }, }), }, From 4b76aba34263c1f6ec485906ad24abefaec5ba3a Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 10:55:19 -0400 Subject: [PATCH 04/19] More test provider extraction --- .../resource/importstate/examplecloud_test.go | 31 ++++++++ .../importstate/import_block_with_id_test.go | 74 ++----------------- 2 files changed, 37 insertions(+), 68 deletions(-) diff --git a/helper/resource/importstate/examplecloud_test.go b/helper/resource/importstate/examplecloud_test.go index 043645daf..208c89321 100644 --- a/helper/resource/importstate/examplecloud_test.go +++ b/helper/resource/importstate/examplecloud_test.go @@ -4,9 +4,40 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" ) +func examplecloudDataSource() testprovider.DataSource { + return testprovider.DataSource{ + ReadResponse: &datasource.ReadResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "datasource-test"), + }, + ), + }, + SchemaResponse: &datasource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + } +} + func examplecloudResource() testprovider.Resource { return testprovider.Resource{ CreateResponse: &resource.CreateResponse{ diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index 0339ae57b..08a52498f 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -8,7 +8,6 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -103,74 +102,10 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ DataSources: map[string]testprovider.DataSource{ - "examplecloud_thing": { - ReadResponse: &datasource.ReadResponse{ - State: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "datasource-test"), - }, - ), - }, - SchemaResponse: &datasource.SchemaResponse{ - Schema: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "id", - Type: tftypes.String, - Computed: true, - }, - }, - }, - }, - }, - }, + "examplecloud_thing": examplecloudDataSource(), }, Resources: map[string]testprovider.Resource{ - "examplecloud_thing": { - CreateResponse: &resource.CreateResponse{ - NewState: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "resource-test"), - }, - ), - }, - ImportStateResponse: &resource.ImportStateResponse{ - State: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "resource-test"), - }, - ), - }, - SchemaResponse: &resource.SchemaResponse{ - Schema: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "id", - Type: tftypes.String, - Computed: true, - }, - }, - }, - }, - }, - }, + "examplecloud_thing": examplecloudResource(), }, }), }, @@ -178,7 +113,10 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { { Config: ` data "examplecloud_thing" "test" {} - resource "examplecloud_thing" "test" {} + resource "examplecloud_thing" "test" { + name = "somevalue" + location = "westeurope" + } `, }, { From f7076a91b84fa0b1f732d6360744b0153eabafe1 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 11:00:02 -0400 Subject: [PATCH 05/19] Add a test expecting fail when plannable import is not supported --- .../resource/importstate/examplecloud_test.go | 3 ++ .../importstate/import_block_with_id_test.go | 36 +++++++++++++++++++ helper/resource/testing_new_import_state.go | 26 +++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/helper/resource/importstate/examplecloud_test.go b/helper/resource/importstate/examplecloud_test.go index 208c89321..c7086d6c3 100644 --- a/helper/resource/importstate/examplecloud_test.go +++ b/helper/resource/importstate/examplecloud_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package importstate_test import ( diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index 08a52498f..2140f690a 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -91,6 +91,42 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { }) } +func TestTest_TestStep_ImportBlockId_FailWhenPlannableImportIsNotSupported(t *testing.T) { + t.Setenv("TF_ACC_TERRAFORM_PATH", "") + t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.4.7") // Plannable import is supported in Terraform 1.5.0 and later + + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + Config: ` + resource "examplecloud_container" "test" { + location = "westeurope" + name = "somevalue" + }`, + }, + { + Config: ` + resource "examplecloud_container" "test" { + location = "eastus" + name = "somevalue" + }`, + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithId, + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Terraform 1.5.0`), + }, + }, + }) +} + func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { t.Parallel() diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 96be26543..c4016c4a2 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -6,10 +6,12 @@ package resource import ( "context" "fmt" - tfjson "github.com/hashicorp/terraform-json" "reflect" "strings" + "github.com/hashicorp/go-version" + tfjson "github.com/hashicorp/terraform-json" + "github.com/google/go-cmp/cmp" "github.com/mitchellh/go-testing-interface" @@ -21,6 +23,21 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) +func requirePlannableImport(t testing.T, versionUnderTest version.Version) error { + t.Helper() + + minVersion, err := version.NewVersion("1.5.0") + if err != nil { + panic("failed to parse version string") + } + + if versionUnderTest.LessThan(minVersion) { + return fmt.Errorf("ImportState steps require Terraform 1.5.0 or later") + } + + return nil +} + func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfgRaw string, providers *providerFactories, stepNumber int) error { t.Helper() @@ -32,6 +49,13 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } + { + err := requirePlannableImport(t, *helper.TerraformVersion()) + if err != nil { + return err + } + } + configRequest := teststep.PrepareConfigurationRequest{ Directory: step.ConfigDirectory, File: step.ConfigFile, From 6865074e4546fa4a8994cab804a6df72be309b1c Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Thu, 20 Mar 2025 23:31:01 -0400 Subject: [PATCH 06/19] importstate: a working example of ImportState as the first TestStep --- .../importstate/import_as_first_step_test.go | 65 +++++++++++++++++++ .../importstate/import_block_with_id_test.go | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 helper/resource/importstate/import_as_first_step_test.go diff --git a/helper/resource/importstate/import_as_first_step_test.go b/helper/resource/importstate/import_as_first_step_test.go new file mode 100644 index 000000000..c672e7f71 --- /dev/null +++ b/helper/resource/importstate/import_as_first_step_test.go @@ -0,0 +1,65 @@ +package importstate_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_TestStep_Import_AsFirstStep(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ResourceName: "examplecloud_container.test", + ImportStateId: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportCommandWithId, + // ImportStateVerify: true, + Config: `resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" + }`, + ImportStatePersist: true, + ImportStateCheck: func(states []*terraform.InstanceState) error { + if len(states) != 1 { + return fmt.Errorf("expected 1 state; got %d", len(states)) + } + if states[0].ID != "westeurope/somevalue" { + return fmt.Errorf("unexpected ID: %s", states[0].ID) + } + if states[0].Attributes["name"] != "somevalue" { + return fmt.Errorf("unexpected name: %s", states[0].Attributes["name"]) + } + if states[0].Attributes["location"] != "westeurope" { + return fmt.Errorf("unexpected location: %s", states[0].Attributes["location"]) + } + return nil + }, + }, + { + RefreshState: true, + Check: r.TestCheckResourceAttr("examplecloud_container.test", "name", "somevalue"), + }, + }, + }) +} diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index 2140f690a..f23be7aec 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -21,7 +21,7 @@ import ( r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestTest_TestStep_ImportBlockId(t *testing.T) { +func Test_TestStep_ImportBlockId(t *testing.T) { t.Parallel() r.UnitTest(t, r.TestCase{ From 70f740d09c7b8301e0697ae39c21a6bd1d00b770 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Fri, 21 Mar 2025 17:24:25 -0400 Subject: [PATCH 07/19] importstate: a working example of ImportBlockWithId as the first TestStep --- ....go => import_block_as_first_step_test.go} | 4 +- .../import_command_as_first_step_test.go | 65 +++++++++++++++++++ helper/resource/testing_new_import_state.go | 19 +++++- 3 files changed, 84 insertions(+), 4 deletions(-) rename helper/resource/importstate/{import_as_first_step_test.go => import_block_as_first_step_test.go} (95%) create mode 100644 helper/resource/importstate/import_command_as_first_step_test.go diff --git a/helper/resource/importstate/import_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go similarity index 95% rename from helper/resource/importstate/import_as_first_step_test.go rename to helper/resource/importstate/import_block_as_first_step_test.go index c672e7f71..02ff69b22 100644 --- a/helper/resource/importstate/import_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -14,7 +14,7 @@ import ( r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func Test_TestStep_Import_AsFirstStep(t *testing.T) { +func Test_TestStep_ImportBlock_AsFirstStep(t *testing.T) { t.Parallel() r.UnitTest(t, r.TestCase{ @@ -33,7 +33,7 @@ func Test_TestStep_Import_AsFirstStep(t *testing.T) { ResourceName: "examplecloud_container.test", ImportStateId: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportCommandWithId, + ImportStateKind: r.ImportBlockWithId, // ImportStateVerify: true, Config: `resource "examplecloud_container" "test" { name = "somevalue" diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go new file mode 100644 index 000000000..d1b624e44 --- /dev/null +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -0,0 +1,65 @@ +package importstate_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_TestStep_ImportCommand_AsFirstStep(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories needs Terraform 1.0.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ResourceName: "examplecloud_container.test", + ImportStateId: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportCommandWithId, + // ImportStateVerify: true, + Config: `resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" + }`, + ImportStatePersist: true, + ImportStateCheck: func(states []*terraform.InstanceState) error { + if len(states) != 1 { + return fmt.Errorf("expected 1 state; got %d", len(states)) + } + if states[0].ID != "westeurope/somevalue" { + return fmt.Errorf("unexpected ID: %s", states[0].ID) + } + if states[0].Attributes["name"] != "somevalue" { + return fmt.Errorf("unexpected name: %s", states[0].Attributes["name"]) + } + if states[0].Attributes["location"] != "westeurope" { + return fmt.Errorf("unexpected location: %s", states[0].Attributes["location"]) + } + return nil + }, + }, + { + RefreshState: true, + Check: r.TestCheckResourceAttr("examplecloud_container.test", "name", "somevalue"), + }, + }, + }) +} diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index c4016c4a2..79aa5dc11 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -179,10 +179,9 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest t.Fatalf("Error setting test config: %s", err) } - logging.HelperResourceDebug(ctx, "Running Terraform CLI init and import") - if !step.ImportStatePersist { err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform init") return importWd.Init(ctx) }, importWd, providers) if err != nil { @@ -194,6 +193,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest var opts []tfexec.PlanOption err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform plan") return importWd.CreatePlan(ctx, opts...) }, importWd, providers) if err != nil { @@ -203,6 +203,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest var plan *tfjson.Plan err = runProviderCommand(ctx, t, func() error { var err error + logging.HelperResourceDebug(ctx, "Run terraform show") plan, err = importWd.SavedPlan(ctx) return err }, importWd, providers) @@ -211,6 +212,8 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } if plan.ResourceChanges != nil { + logging.HelperResourceDebug(ctx, fmt.Sprintf("ImportBlockWithId: %d resource changes", len(plan.ResourceChanges))) + for _, rc := range plan.ResourceChanges { if rc.Address != step.ResourceName { // we're only interested in the changes for the resource being imported @@ -238,8 +241,18 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } // TODO compare plan to state from previous step + err = runProviderCommand(ctx, t, func() error { + var err error + logging.HelperResourceDebug(ctx, "Run terraform apply") + err = importWd.Apply(ctx) + return err + }, importWd, providers) + if err != nil { + return err + } } else { err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform import") return importWd.Import(ctx, step.ResourceName, importId) }, importWd, providers) if err != nil { @@ -259,6 +272,8 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest t.Fatalf("Error getting state: %s", err) } + logging.HelperResourceDebug(ctx, fmt.Sprintf("State after import: %d resources in the root module", len(importState.RootModule().Resources))) + // Go through the imported state and verify if step.ImportStateCheck != nil { logging.HelperResourceTrace(ctx, "Using TestStep ImportStateCheck") From d8f5ccbc0c2fcfb577129add744351398b8a4468 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 08:49:46 -0400 Subject: [PATCH 08/19] make generate --- helper/resource/importstate/import_block_as_first_step_test.go | 3 +++ .../resource/importstate/import_command_as_first_step_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go index 02ff69b22..5d05d0cf1 100644 --- a/helper/resource/importstate/import_block_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package importstate_test import ( diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go index d1b624e44..de57ab972 100644 --- a/helper/resource/importstate/import_command_as_first_step_test.go +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package importstate_test import ( From 0872e07ef5523589b25d11a16b48ced80c576e12 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 11:36:23 -0400 Subject: [PATCH 09/19] Use PlanChecks --- .../import_block_as_first_step_test.go | 30 +++++++------------ .../import_command_as_first_step_test.go | 4 --- helper/resource/testing.go | 14 +++++++++ helper/resource/testing_new_import_state.go | 12 +++----- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go index 5d05d0cf1..8315cba1f 100644 --- a/helper/resource/importstate/import_block_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -4,14 +4,15 @@ package importstate_test import ( - "fmt" "testing" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" - "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/hashicorp/terraform-plugin-testing/tfversion" r "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -43,26 +44,15 @@ func Test_TestStep_ImportBlock_AsFirstStep(t *testing.T) { location = "westeurope" }`, ImportStatePersist: true, - ImportStateCheck: func(states []*terraform.InstanceState) error { - if len(states) != 1 { - return fmt.Errorf("expected 1 state; got %d", len(states)) - } - if states[0].ID != "westeurope/somevalue" { - return fmt.Errorf("unexpected ID: %s", states[0].ID) - } - if states[0].Attributes["name"] != "somevalue" { - return fmt.Errorf("unexpected name: %s", states[0].Attributes["name"]) - } - if states[0].Attributes["location"] != "westeurope" { - return fmt.Errorf("unexpected location: %s", states[0].Attributes["location"]) - } - return nil + ImportPlanChecks: r.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("examplecloud_container.test", plancheck.ResourceActionNoop), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("id"), knownvalue.StringExact("westeurope/somevalue")), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("name"), knownvalue.StringExact("somevalue")), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("location"), knownvalue.StringExact("westeurope")), + }, }, }, - { - RefreshState: true, - Check: r.TestCheckResourceAttr("examplecloud_container.test", "name", "somevalue"), - }, }, }) } diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go index de57ab972..fddd7c1dc 100644 --- a/helper/resource/importstate/import_command_as_first_step_test.go +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -59,10 +59,6 @@ func Test_TestStep_ImportCommand_AsFirstStep(t *testing.T) { return nil }, }, - { - RefreshState: true, - Check: r.TestCheckResourceAttr("examplecloud_container.test", "name", "somevalue"), - }, }, }) } diff --git a/helper/resource/testing.go b/helper/resource/testing.go index c7bdcd75e..6f20bcc5e 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -679,6 +679,13 @@ type TestStep struct { // Terraform version specific logic in provider testing. ImportStateCheck ImportStateCheckFunc + // ImportPlanChecks allows assertions to be made against the plan file at different points of a plannable import test using a plan check. + // Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package + // + // [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck + // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck + ImportPlanChecks ImportPlanChecks + // ImportStateVerify, if true, will also check that the state values // that are finally put into the state after import match for all the // IDs returned by the Import. Note that this checks for strict equality @@ -810,6 +817,13 @@ type ConfigPlanChecks struct { PostApplyPostRefresh []plancheck.PlanCheck } +// ImportPlanChecks defines the different points in a Import TestStep when plan checks can be run. +type ImportPlanChecks struct { + // PreApply runs all plan checks in the slice. This occurs before the apply of a Import test is run. This slice cannot be populated + // with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PreApply []plancheck.PlanCheck +} + // RefreshPlanChecks defines the different points in a Refresh TestStep when plan checks can be run. type RefreshPlanChecks struct { // PostRefresh runs all plan checks in the slice. This occurs after the refresh of the Refresh test is run. diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index df806545c..8d02d3c62 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -188,6 +188,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } + var plan *tfjson.Plan if step.ImportStateKind == ImportBlockWithResourceIdentity || step.ImportStateKind == ImportBlockWithId { var opts []tfexec.PlanOption @@ -199,7 +200,6 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest return err } - var plan *tfjson.Plan err = runProviderCommand(ctx, t, func() error { var err error logging.HelperResourceDebug(ctx, "Run terraform show") @@ -240,15 +240,11 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } // TODO compare plan to state from previous step - err = runProviderCommand(ctx, t, func() error { - var err error - logging.HelperResourceDebug(ctx, "Run terraform apply") - err = importWd.Apply(ctx) - return err - }, importWd, providers) - if err != nil { + + if err := runPlanChecks(ctx, t, plan, step.ImportPlanChecks.PreApply); err != nil { return err } + } else { err = runProviderCommand(ctx, t, func() error { logging.HelperResourceDebug(ctx, "Run terraform import") From 5d6bf83939b4e7b976f7c1caeb871f42803ce939 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 11:37:57 -0400 Subject: [PATCH 10/19] Simpler test names --- helper/resource/importstate/import_block_as_first_step_test.go | 2 +- .../resource/importstate/import_command_as_first_step_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go index 8315cba1f..59664baa5 100644 --- a/helper/resource/importstate/import_block_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -18,7 +18,7 @@ import ( r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func Test_TestStep_ImportBlock_AsFirstStep(t *testing.T) { +func Test_ImportBlock_AsFirstStep(t *testing.T) { t.Parallel() r.UnitTest(t, r.TestCase{ diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go index fddd7c1dc..2193dfc7e 100644 --- a/helper/resource/importstate/import_command_as_first_step_test.go +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -17,7 +17,7 @@ import ( r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func Test_TestStep_ImportCommand_AsFirstStep(t *testing.T) { +func Test_ImportCommand_AsFirstStep(t *testing.T) { t.Parallel() r.UnitTest(t, r.TestCase{ From f45d8fe2517a533a1d6e9ea6fa69c776b2c8a471 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 11:41:41 -0400 Subject: [PATCH 11/19] Tidy comment --- helper/resource/testing.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 6f20bcc5e..690d282a2 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -817,9 +817,9 @@ type ConfigPlanChecks struct { PostApplyPostRefresh []plancheck.PlanCheck } -// ImportPlanChecks defines the different points in a Import TestStep when plan checks can be run. +// ImportPlanChecks defines the different points in an Import TestStep when plan checks can be run. type ImportPlanChecks struct { - // PreApply runs all plan checks in the slice. This occurs before the apply of a Import test is run. This slice cannot be populated + // PreApply runs all plan checks in the slice. This occurs after the plan of an Import test is computed. This slice cannot be populated // with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. PreApply []plancheck.PlanCheck } From a36cde2044442f5e3f17dd5c9d84424dbe0949f5 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 12:31:00 -0400 Subject: [PATCH 12/19] wip --- .../import_block_as_first_step_test.go | 4 +- .../importstate/import_block_with_id_test.go | 24 +++---- .../import_command_as_first_step_test.go | 8 +-- helper/resource/testing.go | 32 +++++++-- helper/resource/testing_new_import_state.go | 70 ++++++------------- 5 files changed, 62 insertions(+), 76 deletions(-) diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go index 59664baa5..919659ef1 100644 --- a/helper/resource/importstate/import_block_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -23,7 +23,7 @@ func Test_ImportBlock_AsFirstStep(t *testing.T) { r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -37,7 +37,7 @@ func Test_ImportBlock_AsFirstStep(t *testing.T) { ResourceName: "examplecloud_container.test", ImportStateId: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, // ImportStateVerify: true, Config: `resource "examplecloud_container" "test" { name = "somevalue" diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index e30717772..0f65af713 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -26,7 +26,7 @@ func Test_TestStep_ImportBlockId(t *testing.T) { r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -46,7 +46,7 @@ func Test_TestStep_ImportBlockId(t *testing.T) { { ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, }, }, @@ -58,7 +58,7 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -83,9 +83,9 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { }`, ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, - ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test should be a no-op, but got action update with plan(.?)`), + ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test: expected a no-op resource action, got "update" action with plan(.?)`), }, }, }) @@ -122,7 +122,7 @@ func TestTest_TestStep_ImportBlockId_FailWhenPlannableImportIsNotSupported(t *te }`, ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, ExpectError: regexp.MustCompile(`Terraform 1.5.0`), }, @@ -135,7 +135,7 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ @@ -161,7 +161,7 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { { ResourceName: "examplecloud_thing.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateCheck: func(is []*terraform.InstanceState) error { if len(is) > 1 { return fmt.Errorf("expected 1 state, got: %d", len(is)) @@ -204,7 +204,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -292,7 +292,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes }`, ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"password"}, }, @@ -305,7 +305,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) { r.UnitTest(t, r.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ @@ -377,7 +377,7 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) { { ResourceName: "examplecloud_container.test", ImportState: true, - ImportStateKind: r.ImportBlockWithId, + ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"password"}, }, diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go index 2193dfc7e..771ea8dc4 100644 --- a/helper/resource/importstate/import_command_as_first_step_test.go +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -33,11 +33,9 @@ func Test_ImportCommand_AsFirstStep(t *testing.T) { }, Steps: []r.TestStep{ { - ResourceName: "examplecloud_container.test", - ImportStateId: "examplecloud_container.test", - ImportState: true, - ImportStateKind: r.ImportCommandWithId, - // ImportStateVerify: true, + ResourceName: "examplecloud_container.test", + ImportStateId: "examplecloud_container.test", + ImportState: true, Config: `resource "examplecloud_container" "test" { name = "somevalue" location = "westeurope" diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 690d282a2..665655201 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -457,14 +457,32 @@ type ExternalProvider struct { type ImportStateKind byte const ( - // ImportCommandWithId imports the state using the import command - ImportCommandWithId ImportStateKind = iota - // ImportBlockWithId imports the state using an import block with an ID - ImportBlockWithId + // ImportCommandWithID imports the state using the import command + ImportCommandWithID ImportStateKind = iota + + // ImportBlockWithID imports the state using an import block with an ID + ImportBlockWithID + // ImportBlockWithResourceIdentity imports the state using an import block with a resource identity ImportBlockWithResourceIdentity ) +func (kind ImportStateKind) plannable() bool { + return kind == ImportBlockWithID || kind == ImportBlockWithResourceIdentity +} + +func (kind ImportStateKind) resourceIdentity() bool { + return kind == ImportBlockWithResourceIdentity +} + +func (kind ImportStateKind) String() string { + return map[ImportStateKind]string{ + ImportCommandWithID: "ImportCommandWithID", + ImportBlockWithID: "ImportBlockWithID", + ImportBlockWithResourceIdentity: "ImportBlockWithResourceIdentity", + }[kind] +} + // TestStep is a single apply sequence of a test, done within the // context of a state. // @@ -967,17 +985,17 @@ func UnitTest(t testing.T, c TestCase) { Test(t, c) } -func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { +func testResource(name string, state *terraform.State) (*terraform.ResourceState, error) { for _, m := range state.Modules { if len(m.Resources) > 0 { - if v, ok := m.Resources[c.ResourceName]; ok { + if v, ok := m.Resources[name]; ok { return v, nil } } } return nil, fmt.Errorf( - "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) + "Resource specified by ResourceName couldn't be found: %s", name) } // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 8d02d3c62..269f5dd17 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -9,7 +9,6 @@ import ( "reflect" "strings" - "github.com/hashicorp/go-version" tfjson "github.com/hashicorp/terraform-json" "github.com/google/go-cmp/cmp" @@ -21,40 +20,27 @@ import ( "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" "github.com/hashicorp/terraform-plugin-testing/internal/teststep" "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func requirePlannableImport(t testing.T, versionUnderTest version.Version) error { - t.Helper() - - if versionUnderTest.LessThan(tfversion.Version1_5_0) { - return fmt.Errorf( - `ImportState steps using plannable import blocks require Terraform 1.5.0 or later. Either ` + - `upgrade the Terraform version running the test or add a ` + "`TerraformVersionChecks`" + ` to ` + - `the test case to skip this test.` + "\n\n" + - `https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/tfversion-checks#skip-version-checks`) - } - - return nil -} - func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfgRaw string, providers *providerFactories, stepNumber int) error { t.Helper() - // Currently import modes `ImportBlockWithId` and `ImportBlockWithResourceIdentity` cannot support config file or directory - // since these modes append the import block to the configuration automatically - if step.ImportStateKind != ImportCommandWithId { - if step.ConfigFile != nil || step.ConfigDirectory != nil { - t.Fatalf("ImportStateKind %q is not supported for config file or directory", step.ImportStateKind) - } - } - - if step.ImportStateKind != ImportCommandWithId { + // step.ImportStateKind implicitly defaults to the zero-value (ImportCommandWithID) for backward compatibility + kind := step.ImportStateKind + if kind.plannable() { + // Instead of calling [t.Fatal], return an error. This package's unit tests can use [TestStep.ExpectError] to match on the error message. + // An alternative, [plugintest.TestExpectTFatal], does not have access to logged error messages, so it is open to false positives on this + // complex code path. if err := requirePlannableImport(t, *helper.TerraformVersion()); err != nil { return err } } + resourceName := step.ResourceName + if resourceName == "" { + t.Fatal("ResourceName is required for an import state test") + } + configRequest := teststep.PrepareConfigurationRequest{ Directory: step.ConfigDirectory, File: step.ConfigFile, @@ -67,10 +53,6 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest testStepConfig := teststep.Configuration(configRequest) - if step.ResourceName == "" { - t.Fatal("ResourceName is required for an import state test") - } - // get state from check sequence var state *terraform.State var err error @@ -110,7 +92,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest default: logging.HelperResourceTrace(ctx, "Using resource identifier for import identifier") - resource, err := testResource(step, state) + resource, err := testResource(resourceName, state) if err != nil { t.Fatal(err) } @@ -125,6 +107,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest logging.HelperResourceTrace(ctx, fmt.Sprintf("Using import identifier: %s", importId)) + // Append to previous step config unless using ConfigFile or ConfigDirectory if testStepConfig == nil || step.Config != "" { importConfig := step.Config if importConfig == "" { @@ -132,19 +115,8 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest importConfig = cfgRaw } - // Update the test config dependent on the kind of import test being performed - switch step.ImportStateKind { - case ImportBlockWithResourceIdentity: - t.Fatalf("TODO implement me") - case ImportBlockWithId: - importConfig += fmt.Sprintf(` - import { - to = %s - id = %q - } - `, step.ResourceName, importId) - default: - // Not an import block test so nothing to do here + if kind.plannable() { + importConfig = appendImportBlock(importConfig, resourceName, importId) } confRequest := teststep.PrepareConfigurationRequest{ @@ -170,7 +142,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest importWd = wd } else { importWd = helper.RequireNewWorkingDir(ctx, t, "") - defer importWd.Close() + defer importWd.Close() //nolint:errcheck } err = importWd.SetConfig(ctx, testStepConfig, step.ConfigVariables) @@ -189,7 +161,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } var plan *tfjson.Plan - if step.ImportStateKind == ImportBlockWithResourceIdentity || step.ImportStateKind == ImportBlockWithId { + if kind.plannable() { var opts []tfexec.PlanOption err = runProviderCommand(ctx, t, func() error { @@ -214,7 +186,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest logging.HelperResourceDebug(ctx, fmt.Sprintf("ImportBlockWithId: %d resource changes", len(plan.ResourceChanges))) for _, rc := range plan.ResourceChanges { - if rc.Address != step.ResourceName { + if rc.Address != resourceName { // we're only interested in the changes for the resource being imported continue } @@ -232,7 +204,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest return fmt.Errorf("retrieving formatted plan output: %w", err) } - return fmt.Errorf("importing resource %s should be a no-op, but got action %s with plan \\nstdout:\\n\\n%s", rc.Address, action, stdout) + return fmt.Errorf("importing resource %s: expected a no-op resource action, got %q action with plan \nstdout:\n\n%s", rc.Address, action, stdout) } } } @@ -244,11 +216,9 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest if err := runPlanChecks(ctx, t, plan, step.ImportPlanChecks.PreApply); err != nil { return err } - } else { err = runProviderCommand(ctx, t, func() error { - logging.HelperResourceDebug(ctx, "Run terraform import") - return importWd.Import(ctx, step.ResourceName, importId) + return importWd.Import(ctx, resourceName, importId) }, importWd, providers) if err != nil { return err From 3b822d6ca9050a86bc2d93019604f4f026d1589c Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 12:41:31 -0400 Subject: [PATCH 13/19] fixup! wip --- helper/resource/testing.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 665655201..407425ee3 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -471,10 +471,6 @@ func (kind ImportStateKind) plannable() bool { return kind == ImportBlockWithID || kind == ImportBlockWithResourceIdentity } -func (kind ImportStateKind) resourceIdentity() bool { - return kind == ImportBlockWithResourceIdentity -} - func (kind ImportStateKind) String() string { return map[ImportStateKind]string{ ImportCommandWithID: "ImportCommandWithID", From 3134be202e2fc12e693b47941f514ce74bbb3e9e Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 12:41:40 -0400 Subject: [PATCH 14/19] Extract functions --- helper/resource/testing_new_import_state.go | 75 ++++++++++++++------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 269f5dd17..2eabb7dcc 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -9,6 +9,8 @@ import ( "reflect" "strings" + "github.com/hashicorp/go-version" + tfjson "github.com/hashicorp/terraform-json" "github.com/google/go-cmp/cmp" @@ -20,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" "github.com/hashicorp/terraform-plugin-testing/internal/teststep" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" ) func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfgRaw string, providers *providerFactories, stepNumber int) error { @@ -242,29 +245,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest // Go through the imported state and verify if step.ImportStateCheck != nil { logging.HelperResourceTrace(ctx, "Using TestStep ImportStateCheck") - - var states []*terraform.InstanceState - for address, r := range importState.RootModule().Resources { - if strings.HasPrefix(address, "data.") { - continue - } - - if r.Primary == nil { - continue - } - - is := r.Primary.DeepCopy() //nolint:staticcheck // legacy usage - is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type - states = append(states, is) - } - - logging.HelperResourceDebug(ctx, "Calling TestStep ImportStateCheck") - - if err := step.ImportStateCheck(states); err != nil { - t.Fatal(err) - } - - logging.HelperResourceDebug(ctx, "Called TestStep ImportStateCheck") + runImportStateCheckFunction(ctx, t, importState, step) } // Verify that all the states match @@ -407,3 +388,51 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest return nil } + +func appendImportBlock(config string, resourceName string, importID string) string { + return config + fmt.Sprintf(``+"\n"+ + `import {`+"\n"+ + ` to = %s`+"\n"+ + ` id = %q`+"\n"+ + `}`, + resourceName, importID) +} + +func requirePlannableImport(t testing.T, versionUnderTest version.Version) error { + t.Helper() + + if versionUnderTest.LessThan(tfversion.Version1_5_0) { + return fmt.Errorf( + `ImportState steps using plannable import blocks require Terraform 1.5.0 or later. Either ` + + `upgrade the Terraform version running the test or add a ` + "`TerraformVersionChecks`" + ` to ` + + `the test case to skip this test.` + "\n\n" + + `https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/tfversion-checks#skip-version-checks`) + } + + return nil +} + +func runImportStateCheckFunction(ctx context.Context, t testing.T, importState *terraform.State, step TestStep) { + var states []*terraform.InstanceState + for address, r := range importState.RootModule().Resources { + if strings.HasPrefix(address, "data.") { + continue + } + + if r.Primary == nil { + continue + } + + is := r.Primary.DeepCopy() //nolint:staticcheck // legacy usage + is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type + states = append(states, is) + } + + logging.HelperResourceTrace(ctx, "Calling TestStep ImportStateCheck") + + if err := step.ImportStateCheck(states); err != nil { + t.Fatal(err) + } + + logging.HelperResourceTrace(ctx, "Called TestStep ImportStateCheck") +} From 99f1fae99b1c80312b89e198d9a7a08c498cedc5 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 12:54:17 -0400 Subject: [PATCH 15/19] Add tests for import block in ConfigFile and ConfigDirectory --- .../import_block_in_config_directory_test.go | 50 +++++++++++++++++++ .../import_block_in_config_file_test.go | 50 +++++++++++++++++++ .../testdata/1/examplecloud_container.tf | 7 +++ .../2/examplecloud_container_import.tf | 12 +++++ 4 files changed, 119 insertions(+) create mode 100644 helper/resource/importstate/import_block_in_config_directory_test.go create mode 100644 helper/resource/importstate/import_block_in_config_file_test.go create mode 100644 helper/resource/importstate/testdata/1/examplecloud_container.tf create mode 100644 helper/resource/importstate/testdata/2/examplecloud_container_import.tf diff --git a/helper/resource/importstate/import_block_in_config_directory_test.go b/helper/resource/importstate/import_block_in_config_directory_test.go new file mode 100644 index 000000000..94a49fcd9 --- /dev/null +++ b/helper/resource/importstate/import_block_in_config_directory_test.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package importstate_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_ImportBlock_InConfigDirectory(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ConfigDirectory: func(config.TestStepConfigRequest) string { + return `testdata/1` + }, + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + ImportStateVerify: true, + ConfigDirectory: func(config.TestStepConfigRequest) string { + return `testdata/2` + }, + }, + }, + }) +} diff --git a/helper/resource/importstate/import_block_in_config_file_test.go b/helper/resource/importstate/import_block_in_config_file_test.go new file mode 100644 index 000000000..94466ff98 --- /dev/null +++ b/helper/resource/importstate/import_block_in_config_file_test.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package importstate_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_ImportBlock_InConfigFile(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ConfigFile: func(config.TestStepConfigRequest) string { + return `testdata/1/examplecloud_container.tf` + }, + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + ImportStateVerify: true, + ConfigFile: func(config.TestStepConfigRequest) string { + return `testdata/2/examplecloud_container_import.tf` + }, + }, + }, + }) +} diff --git a/helper/resource/importstate/testdata/1/examplecloud_container.tf b/helper/resource/importstate/testdata/1/examplecloud_container.tf new file mode 100644 index 000000000..ccfb698e6 --- /dev/null +++ b/helper/resource/importstate/testdata/1/examplecloud_container.tf @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" +} diff --git a/helper/resource/importstate/testdata/2/examplecloud_container_import.tf b/helper/resource/importstate/testdata/2/examplecloud_container_import.tf new file mode 100644 index 000000000..f7e9411f9 --- /dev/null +++ b/helper/resource/importstate/testdata/2/examplecloud_container_import.tf @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" +} + +import { + to = examplecloud_container.test + id = "examplecloud_container.test" +} From 42f36b733ba9d60adbaf4f2c030e85125c9019c5 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 13:05:11 -0400 Subject: [PATCH 16/19] Mark test helper function --- helper/resource/testing_new_import_state.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 2eabb7dcc..3bd46f1be 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -413,6 +413,8 @@ func requirePlannableImport(t testing.T, versionUnderTest version.Version) error } func runImportStateCheckFunction(ctx context.Context, t testing.T, importState *terraform.State, step TestStep) { + t.Helper() + var states []*terraform.InstanceState for address, r := range importState.RootModule().Resources { if strings.HasPrefix(address, "data.") { From 024ac4973abb8b9c1483c9f80ddef6da32cb9ed5 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Mon, 24 Mar 2025 13:21:31 -0400 Subject: [PATCH 17/19] minimize diff --- helper/resource/testing_new_import_state.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 3bd46f1be..7581892dc 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -39,11 +39,6 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } - resourceName := step.ResourceName - if resourceName == "" { - t.Fatal("ResourceName is required for an import state test") - } - configRequest := teststep.PrepareConfigurationRequest{ Directory: step.ConfigDirectory, File: step.ConfigFile, @@ -56,6 +51,11 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest testStepConfig := teststep.Configuration(configRequest) + resourceName := step.ResourceName + if resourceName == "" { + t.Fatal("ResourceName is required for an import state test") + } + // get state from check sequence var state *terraform.State var err error From 794a5666cc3882fe66809be4cd016c3570910e3b Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Wed, 26 Mar 2025 11:49:22 -0400 Subject: [PATCH 18/19] Update helper/resource/testing.go Co-authored-by: Austin Valle --- helper/resource/testing.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 407425ee3..f01b43b9d 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -457,10 +457,10 @@ type ExternalProvider struct { type ImportStateKind byte const ( - // ImportCommandWithID imports the state using the import command + // ImportCommandWithID tests import by using the ID string with the `terraform import` command ImportCommandWithID ImportStateKind = iota - // ImportBlockWithID imports the state using an import block with an ID + // ImportBlockWithID tests import by using the ID string in an import configuration block with the `terraform plan` command ImportBlockWithID // ImportBlockWithResourceIdentity imports the state using an import block with a resource identity From a6d4bf24a403fb5abe96f3f568d06b2e40270641 Mon Sep 17 00:00:00 2001 From: Baraa Basata Date: Wed, 26 Mar 2025 11:54:54 -0400 Subject: [PATCH 19/19] Use Static{File,Directory} helper functions --- .../importstate/import_block_in_config_directory_test.go | 8 ++------ .../importstate/import_block_in_config_file_test.go | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/helper/resource/importstate/import_block_in_config_directory_test.go b/helper/resource/importstate/import_block_in_config_directory_test.go index 94a49fcd9..2cb9ab6e1 100644 --- a/helper/resource/importstate/import_block_in_config_directory_test.go +++ b/helper/resource/importstate/import_block_in_config_directory_test.go @@ -32,18 +32,14 @@ func Test_ImportBlock_InConfigDirectory(t *testing.T) { }, Steps: []r.TestStep{ { - ConfigDirectory: func(config.TestStepConfigRequest) string { - return `testdata/1` - }, + ConfigDirectory: config.StaticDirectory(`testdata/1`), }, { ResourceName: "examplecloud_container.test", ImportState: true, ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, - ConfigDirectory: func(config.TestStepConfigRequest) string { - return `testdata/2` - }, + ConfigDirectory: config.StaticDirectory(`testdata/2`), }, }, }) diff --git a/helper/resource/importstate/import_block_in_config_file_test.go b/helper/resource/importstate/import_block_in_config_file_test.go index 94466ff98..8b983e310 100644 --- a/helper/resource/importstate/import_block_in_config_file_test.go +++ b/helper/resource/importstate/import_block_in_config_file_test.go @@ -32,18 +32,14 @@ func Test_ImportBlock_InConfigFile(t *testing.T) { }, Steps: []r.TestStep{ { - ConfigFile: func(config.TestStepConfigRequest) string { - return `testdata/1/examplecloud_container.tf` - }, + ConfigFile: config.StaticFile(`testdata/1/examplecloud_container.tf`), }, { ResourceName: "examplecloud_container.test", ImportState: true, ImportStateKind: r.ImportBlockWithID, ImportStateVerify: true, - ConfigFile: func(config.TestStepConfigRequest) string { - return `testdata/2/examplecloud_container_import.tf` - }, + ConfigFile: config.StaticFile(`testdata/2/examplecloud_container_import.tf`), }, }, })