diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 19cbe4a1f..17358f93d 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -149,6 +149,11 @@ type TestStep struct { // used to verify that the resulting value of ImportState has the // proper resources, IDs, and attributes. ImportStateCheck ImportStateCheckFunc + + // 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. + ImportStateVerify bool } // Test performs an acceptance test on a resource. diff --git a/helper/resource/testing_import_state.go b/helper/resource/testing_import_state.go index d93bc76eb..c110ec950 100644 --- a/helper/resource/testing_import_state.go +++ b/helper/resource/testing_import_state.go @@ -1,8 +1,11 @@ package resource import ( + "fmt" "log" + "reflect" + "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/terraform" ) @@ -36,11 +39,11 @@ func testStepImportState( return state, err } - // TODO: ImportOpts needs a flag to read a config module so it - // can load our provider config without env vars. - // Do the import! newState, err := ctx.Import(&terraform.ImportOpts{ + // Set the module so that any provider config is loaded + Module: mod, + Targets: []*terraform.ImportTarget{ &terraform.ImportTarget{ Addr: step.ResourceName, @@ -66,6 +69,47 @@ func testStepImportState( } } + // Verify that all the states match + if step.ImportStateVerify { + new := newState.RootModule().Resources + old := state.RootModule().Resources + for _, r := range new { + // Find the existing resource + var oldR *terraform.ResourceState + for _, r2 := range old { + if r2.Primary != nil && r2.Primary.ID == r.Primary.ID { + oldR = r2 + break + } + } + if oldR == nil { + return state, fmt.Errorf( + "Failed state verification, resource with ID %s not found", + r.Primary.ID) + } + + // Compare their attributes + actual := r.Primary.Attributes + expected := oldR.Primary.Attributes + if !reflect.DeepEqual(actual, expected) { + // Determine only the different attributes + for k, v := range expected { + if av, ok := actual[k]; ok && v == av { + delete(expected, k) + delete(actual, k) + } + } + + spewConf := spew.NewDefaultConfig() + spewConf.SortKeys = true + return state, fmt.Errorf( + "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ + "\n\n%s\n\n%s", + spewConf.Sdump(actual), spewConf.Sdump(expected)) + } + } + } + // Return the old state (non-imported) so we don't change anything. return state, nil } diff --git a/helper/resource/testing_import_state_test.go b/helper/resource/testing_import_state_test.go index bcb6372eb..56e6a4845 100644 --- a/helper/resource/testing_import_state_test.go +++ b/helper/resource/testing_import_state_test.go @@ -177,3 +177,134 @@ func TestTest_importStateDetectId(t *testing.T) { t.Fatal("didn't call check") } } + +func TestTest_importStateVerify(t *testing.T) { + mp := testProvider() + mp.DiffReturn = nil + mp.ApplyFn = func( + info *terraform.InstanceInfo, + state *terraform.InstanceState, + diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { + if !diff.Destroy { + return &terraform.InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "foo": "bar", + }, + }, nil + } + + return nil, nil + } + + mp.RefreshFn = func( + i *terraform.InstanceInfo, + s *terraform.InstanceState) (*terraform.InstanceState, error) { + if len(s.Attributes) == 0 { + s.Attributes = map[string]string{ + "id": s.ID, + "foo": "bar", + } + } + + return s, nil + } + + mp.ImportStateFn = func( + info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { + if id != "foo" { + return nil, fmt.Errorf("bad import ID: %s", id) + } + + return []*terraform.InstanceState{ + &terraform.InstanceState{ + ID: "foo", + Ephemeral: terraform.EphemeralState{Type: "test_instance"}, + }, + }, nil + } + + mt := new(mockT) + Test(mt, TestCase{ + Providers: map[string]terraform.ResourceProvider{ + "test": mp, + }, + + Steps: []TestStep{ + TestStep{ + Config: testConfigStr, + }, + TestStep{ + ResourceName: "test_instance.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if mt.failed() { + t.Fatalf("test failed: %s", mt.failMessage()) + } +} + +func TestTest_importStateVerifyFail(t *testing.T) { + mp := testProvider() + mp.DiffReturn = nil + mp.ApplyFn = func( + info *terraform.InstanceInfo, + state *terraform.InstanceState, + diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { + if !diff.Destroy { + return &terraform.InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "foo": "bar", + }, + }, nil + } + + return nil, nil + } + + mp.RefreshFn = func( + i *terraform.InstanceInfo, + s *terraform.InstanceState) (*terraform.InstanceState, error) { + return s, nil + } + + mp.ImportStateFn = func( + info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { + if id != "foo" { + return nil, fmt.Errorf("bad import ID: %s", id) + } + + return []*terraform.InstanceState{ + &terraform.InstanceState{ + ID: "foo", + Ephemeral: terraform.EphemeralState{Type: "test_instance"}, + }, + }, nil + } + + mt := new(mockT) + Test(mt, TestCase{ + Providers: map[string]terraform.ResourceProvider{ + "test": mp, + }, + + Steps: []TestStep{ + TestStep{ + Config: testConfigStr, + }, + TestStep{ + ResourceName: "test_instance.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if !mt.failed() { + t.Fatalf("test should fail") + } +}