github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/helper/schema/resource_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/config"
    11  	"github.com/hashicorp/terraform/terraform"
    12  )
    13  
    14  func TestResourceApply_create(t *testing.T) {
    15  	r := &Resource{
    16  		SchemaVersion: 2,
    17  		Schema: map[string]*Schema{
    18  			"foo": &Schema{
    19  				Type:     TypeInt,
    20  				Optional: true,
    21  			},
    22  		},
    23  	}
    24  
    25  	called := false
    26  	r.Create = func(d *ResourceData, m interface{}) error {
    27  		called = true
    28  		d.SetId("foo")
    29  		return nil
    30  	}
    31  
    32  	var s *terraform.InstanceState = nil
    33  
    34  	d := &terraform.InstanceDiff{
    35  		Attributes: map[string]*terraform.ResourceAttrDiff{
    36  			"foo": &terraform.ResourceAttrDiff{
    37  				New: "42",
    38  			},
    39  		},
    40  	}
    41  
    42  	actual, err := r.Apply(s, d, nil)
    43  	if err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	if !called {
    48  		t.Fatal("not called")
    49  	}
    50  
    51  	expected := &terraform.InstanceState{
    52  		ID: "foo",
    53  		Attributes: map[string]string{
    54  			"id":  "foo",
    55  			"foo": "42",
    56  		},
    57  		Meta: map[string]interface{}{
    58  			"schema_version": "2",
    59  		},
    60  	}
    61  
    62  	if !reflect.DeepEqual(actual, expected) {
    63  		t.Fatalf("bad: %#v", actual)
    64  	}
    65  }
    66  
    67  func TestResourceApply_Timeout_state(t *testing.T) {
    68  	r := &Resource{
    69  		SchemaVersion: 2,
    70  		Schema: map[string]*Schema{
    71  			"foo": &Schema{
    72  				Type:     TypeInt,
    73  				Optional: true,
    74  			},
    75  		},
    76  		Timeouts: &ResourceTimeout{
    77  			Create: DefaultTimeout(40 * time.Minute),
    78  			Update: DefaultTimeout(80 * time.Minute),
    79  			Delete: DefaultTimeout(40 * time.Minute),
    80  		},
    81  	}
    82  
    83  	called := false
    84  	r.Create = func(d *ResourceData, m interface{}) error {
    85  		called = true
    86  		d.SetId("foo")
    87  		return nil
    88  	}
    89  
    90  	var s *terraform.InstanceState = nil
    91  
    92  	d := &terraform.InstanceDiff{
    93  		Attributes: map[string]*terraform.ResourceAttrDiff{
    94  			"foo": &terraform.ResourceAttrDiff{
    95  				New: "42",
    96  			},
    97  		},
    98  	}
    99  
   100  	diffTimeout := &ResourceTimeout{
   101  		Create: DefaultTimeout(40 * time.Minute),
   102  		Update: DefaultTimeout(80 * time.Minute),
   103  		Delete: DefaultTimeout(40 * time.Minute),
   104  	}
   105  
   106  	if err := diffTimeout.DiffEncode(d); err != nil {
   107  		t.Fatalf("Error encoding timeout to diff: %s", err)
   108  	}
   109  
   110  	actual, err := r.Apply(s, d, nil)
   111  	if err != nil {
   112  		t.Fatalf("err: %s", err)
   113  	}
   114  
   115  	if !called {
   116  		t.Fatal("not called")
   117  	}
   118  
   119  	expected := &terraform.InstanceState{
   120  		ID: "foo",
   121  		Attributes: map[string]string{
   122  			"id":  "foo",
   123  			"foo": "42",
   124  		},
   125  		Meta: map[string]interface{}{
   126  			"schema_version": "2",
   127  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   128  		},
   129  	}
   130  
   131  	if !reflect.DeepEqual(actual, expected) {
   132  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   133  	}
   134  }
   135  
   136  // Regression test to ensure that the meta data is read from state, if a
   137  // resource is destroyed and the timeout meta is no longer available from the
   138  // config
   139  func TestResourceApply_Timeout_destroy(t *testing.T) {
   140  	timeouts := &ResourceTimeout{
   141  		Create: DefaultTimeout(40 * time.Minute),
   142  		Update: DefaultTimeout(80 * time.Minute),
   143  		Delete: DefaultTimeout(40 * time.Minute),
   144  	}
   145  
   146  	r := &Resource{
   147  		Schema: map[string]*Schema{
   148  			"foo": &Schema{
   149  				Type:     TypeInt,
   150  				Optional: true,
   151  			},
   152  		},
   153  		Timeouts: timeouts,
   154  	}
   155  
   156  	called := false
   157  	var delTimeout time.Duration
   158  	r.Delete = func(d *ResourceData, m interface{}) error {
   159  		delTimeout = d.Timeout(TimeoutDelete)
   160  		called = true
   161  		return nil
   162  	}
   163  
   164  	s := &terraform.InstanceState{
   165  		ID: "bar",
   166  	}
   167  
   168  	if err := timeouts.StateEncode(s); err != nil {
   169  		t.Fatalf("Error encoding to state: %s", err)
   170  	}
   171  
   172  	d := &terraform.InstanceDiff{
   173  		Destroy: true,
   174  	}
   175  
   176  	actual, err := r.Apply(s, d, nil)
   177  	if err != nil {
   178  		t.Fatalf("err: %s", err)
   179  	}
   180  
   181  	if !called {
   182  		t.Fatal("delete not called")
   183  	}
   184  
   185  	if *timeouts.Delete != delTimeout {
   186  		t.Fatalf("timeouts don't match, expected (%#v), got (%#v)", timeouts.Delete, delTimeout)
   187  	}
   188  
   189  	if actual != nil {
   190  		t.Fatalf("bad: %#v", actual)
   191  	}
   192  }
   193  
   194  func TestResourceDiff_Timeout_diff(t *testing.T) {
   195  	r := &Resource{
   196  		Schema: map[string]*Schema{
   197  			"foo": &Schema{
   198  				Type:     TypeInt,
   199  				Optional: true,
   200  			},
   201  		},
   202  		Timeouts: &ResourceTimeout{
   203  			Create: DefaultTimeout(40 * time.Minute),
   204  			Update: DefaultTimeout(80 * time.Minute),
   205  			Delete: DefaultTimeout(40 * time.Minute),
   206  		},
   207  	}
   208  
   209  	r.Create = func(d *ResourceData, m interface{}) error {
   210  		d.SetId("foo")
   211  		return nil
   212  	}
   213  
   214  	raw, err := config.NewRawConfig(
   215  		map[string]interface{}{
   216  			"foo": 42,
   217  			"timeouts": []map[string]interface{}{
   218  				map[string]interface{}{
   219  					"create": "2h",
   220  				}},
   221  		})
   222  	if err != nil {
   223  		t.Fatalf("err: %s", err)
   224  	}
   225  
   226  	var s *terraform.InstanceState = nil
   227  	conf := terraform.NewResourceConfig(raw)
   228  
   229  	actual, err := r.Diff(s, conf)
   230  	if err != nil {
   231  		t.Fatalf("err: %s", err)
   232  	}
   233  
   234  	expected := &terraform.InstanceDiff{
   235  		Attributes: map[string]*terraform.ResourceAttrDiff{
   236  			"foo": &terraform.ResourceAttrDiff{
   237  				New: "42",
   238  			},
   239  		},
   240  	}
   241  
   242  	diffTimeout := &ResourceTimeout{
   243  		Create: DefaultTimeout(120 * time.Minute),
   244  		Update: DefaultTimeout(80 * time.Minute),
   245  		Delete: DefaultTimeout(40 * time.Minute),
   246  	}
   247  
   248  	if err := diffTimeout.DiffEncode(expected); err != nil {
   249  		t.Fatalf("Error encoding timeout to diff: %s", err)
   250  	}
   251  
   252  	if !reflect.DeepEqual(actual, expected) {
   253  		t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   254  	}
   255  }
   256  
   257  func TestResourceApply_destroy(t *testing.T) {
   258  	r := &Resource{
   259  		Schema: map[string]*Schema{
   260  			"foo": &Schema{
   261  				Type:     TypeInt,
   262  				Optional: true,
   263  			},
   264  		},
   265  	}
   266  
   267  	called := false
   268  	r.Delete = func(d *ResourceData, m interface{}) error {
   269  		called = true
   270  		return nil
   271  	}
   272  
   273  	s := &terraform.InstanceState{
   274  		ID: "bar",
   275  	}
   276  
   277  	d := &terraform.InstanceDiff{
   278  		Destroy: true,
   279  	}
   280  
   281  	actual, err := r.Apply(s, d, nil)
   282  	if err != nil {
   283  		t.Fatalf("err: %s", err)
   284  	}
   285  
   286  	if !called {
   287  		t.Fatal("delete not called")
   288  	}
   289  
   290  	if actual != nil {
   291  		t.Fatalf("bad: %#v", actual)
   292  	}
   293  }
   294  
   295  func TestResourceApply_destroyCreate(t *testing.T) {
   296  	r := &Resource{
   297  		Schema: map[string]*Schema{
   298  			"foo": &Schema{
   299  				Type:     TypeInt,
   300  				Optional: true,
   301  			},
   302  
   303  			"tags": &Schema{
   304  				Type:     TypeMap,
   305  				Optional: true,
   306  				Computed: true,
   307  			},
   308  		},
   309  	}
   310  
   311  	change := false
   312  	r.Create = func(d *ResourceData, m interface{}) error {
   313  		change = d.HasChange("tags")
   314  		d.SetId("foo")
   315  		return nil
   316  	}
   317  	r.Delete = func(d *ResourceData, m interface{}) error {
   318  		return nil
   319  	}
   320  
   321  	var s *terraform.InstanceState = &terraform.InstanceState{
   322  		ID: "bar",
   323  		Attributes: map[string]string{
   324  			"foo":       "bar",
   325  			"tags.Name": "foo",
   326  		},
   327  	}
   328  
   329  	d := &terraform.InstanceDiff{
   330  		Attributes: map[string]*terraform.ResourceAttrDiff{
   331  			"foo": &terraform.ResourceAttrDiff{
   332  				New:         "42",
   333  				RequiresNew: true,
   334  			},
   335  			"tags.Name": &terraform.ResourceAttrDiff{
   336  				Old:         "foo",
   337  				New:         "foo",
   338  				RequiresNew: true,
   339  			},
   340  		},
   341  	}
   342  
   343  	actual, err := r.Apply(s, d, nil)
   344  	if err != nil {
   345  		t.Fatalf("err: %s", err)
   346  	}
   347  
   348  	if !change {
   349  		t.Fatal("should have change")
   350  	}
   351  
   352  	expected := &terraform.InstanceState{
   353  		ID: "foo",
   354  		Attributes: map[string]string{
   355  			"id":        "foo",
   356  			"foo":       "42",
   357  			"tags.%":    "1",
   358  			"tags.Name": "foo",
   359  		},
   360  	}
   361  
   362  	if !reflect.DeepEqual(actual, expected) {
   363  		t.Fatalf("bad: %#v", actual)
   364  	}
   365  }
   366  
   367  func TestResourceApply_destroyPartial(t *testing.T) {
   368  	r := &Resource{
   369  		Schema: map[string]*Schema{
   370  			"foo": &Schema{
   371  				Type:     TypeInt,
   372  				Optional: true,
   373  			},
   374  		},
   375  		SchemaVersion: 3,
   376  	}
   377  
   378  	r.Delete = func(d *ResourceData, m interface{}) error {
   379  		d.Set("foo", 42)
   380  		return fmt.Errorf("some error")
   381  	}
   382  
   383  	s := &terraform.InstanceState{
   384  		ID: "bar",
   385  		Attributes: map[string]string{
   386  			"foo": "12",
   387  		},
   388  	}
   389  
   390  	d := &terraform.InstanceDiff{
   391  		Destroy: true,
   392  	}
   393  
   394  	actual, err := r.Apply(s, d, nil)
   395  	if err == nil {
   396  		t.Fatal("should error")
   397  	}
   398  
   399  	expected := &terraform.InstanceState{
   400  		ID: "bar",
   401  		Attributes: map[string]string{
   402  			"id":  "bar",
   403  			"foo": "42",
   404  		},
   405  		Meta: map[string]interface{}{
   406  			"schema_version": "3",
   407  		},
   408  	}
   409  
   410  	if !reflect.DeepEqual(actual, expected) {
   411  		t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual)
   412  	}
   413  }
   414  
   415  func TestResourceApply_update(t *testing.T) {
   416  	r := &Resource{
   417  		Schema: map[string]*Schema{
   418  			"foo": &Schema{
   419  				Type:     TypeInt,
   420  				Optional: true,
   421  			},
   422  		},
   423  	}
   424  
   425  	r.Update = func(d *ResourceData, m interface{}) error {
   426  		d.Set("foo", 42)
   427  		return nil
   428  	}
   429  
   430  	s := &terraform.InstanceState{
   431  		ID: "foo",
   432  		Attributes: map[string]string{
   433  			"foo": "12",
   434  		},
   435  	}
   436  
   437  	d := &terraform.InstanceDiff{
   438  		Attributes: map[string]*terraform.ResourceAttrDiff{
   439  			"foo": &terraform.ResourceAttrDiff{
   440  				New: "13",
   441  			},
   442  		},
   443  	}
   444  
   445  	actual, err := r.Apply(s, d, nil)
   446  	if err != nil {
   447  		t.Fatalf("err: %s", err)
   448  	}
   449  
   450  	expected := &terraform.InstanceState{
   451  		ID: "foo",
   452  		Attributes: map[string]string{
   453  			"id":  "foo",
   454  			"foo": "42",
   455  		},
   456  	}
   457  
   458  	if !reflect.DeepEqual(actual, expected) {
   459  		t.Fatalf("bad: %#v", actual)
   460  	}
   461  }
   462  
   463  func TestResourceApply_updateNoCallback(t *testing.T) {
   464  	r := &Resource{
   465  		Schema: map[string]*Schema{
   466  			"foo": &Schema{
   467  				Type:     TypeInt,
   468  				Optional: true,
   469  			},
   470  		},
   471  	}
   472  
   473  	r.Update = nil
   474  
   475  	s := &terraform.InstanceState{
   476  		ID: "foo",
   477  		Attributes: map[string]string{
   478  			"foo": "12",
   479  		},
   480  	}
   481  
   482  	d := &terraform.InstanceDiff{
   483  		Attributes: map[string]*terraform.ResourceAttrDiff{
   484  			"foo": &terraform.ResourceAttrDiff{
   485  				New: "13",
   486  			},
   487  		},
   488  	}
   489  
   490  	actual, err := r.Apply(s, d, nil)
   491  	if err == nil {
   492  		t.Fatal("should error")
   493  	}
   494  
   495  	expected := &terraform.InstanceState{
   496  		ID: "foo",
   497  		Attributes: map[string]string{
   498  			"foo": "12",
   499  		},
   500  	}
   501  
   502  	if !reflect.DeepEqual(actual, expected) {
   503  		t.Fatalf("bad: %#v", actual)
   504  	}
   505  }
   506  
   507  func TestResourceApply_isNewResource(t *testing.T) {
   508  	r := &Resource{
   509  		Schema: map[string]*Schema{
   510  			"foo": &Schema{
   511  				Type:     TypeString,
   512  				Optional: true,
   513  			},
   514  		},
   515  	}
   516  
   517  	updateFunc := func(d *ResourceData, m interface{}) error {
   518  		d.Set("foo", "updated")
   519  		if d.IsNewResource() {
   520  			d.Set("foo", "new-resource")
   521  		}
   522  		return nil
   523  	}
   524  	r.Create = func(d *ResourceData, m interface{}) error {
   525  		d.SetId("foo")
   526  		d.Set("foo", "created")
   527  		return updateFunc(d, m)
   528  	}
   529  	r.Update = updateFunc
   530  
   531  	d := &terraform.InstanceDiff{
   532  		Attributes: map[string]*terraform.ResourceAttrDiff{
   533  			"foo": &terraform.ResourceAttrDiff{
   534  				New: "bla-blah",
   535  			},
   536  		},
   537  	}
   538  
   539  	// positive test
   540  	var s *terraform.InstanceState = nil
   541  
   542  	actual, err := r.Apply(s, d, nil)
   543  	if err != nil {
   544  		t.Fatalf("err: %s", err)
   545  	}
   546  
   547  	expected := &terraform.InstanceState{
   548  		ID: "foo",
   549  		Attributes: map[string]string{
   550  			"id":  "foo",
   551  			"foo": "new-resource",
   552  		},
   553  	}
   554  
   555  	if !reflect.DeepEqual(actual, expected) {
   556  		t.Fatalf("actual: %#v\nexpected: %#v",
   557  			actual, expected)
   558  	}
   559  
   560  	// negative test
   561  	s = &terraform.InstanceState{
   562  		ID: "foo",
   563  		Attributes: map[string]string{
   564  			"id":  "foo",
   565  			"foo": "new-resource",
   566  		},
   567  	}
   568  
   569  	actual, err = r.Apply(s, d, nil)
   570  	if err != nil {
   571  		t.Fatalf("err: %s", err)
   572  	}
   573  
   574  	expected = &terraform.InstanceState{
   575  		ID: "foo",
   576  		Attributes: map[string]string{
   577  			"id":  "foo",
   578  			"foo": "updated",
   579  		},
   580  	}
   581  
   582  	if !reflect.DeepEqual(actual, expected) {
   583  		t.Fatalf("actual: %#v\nexpected: %#v",
   584  			actual, expected)
   585  	}
   586  }
   587  
   588  func TestResourceInternalValidate(t *testing.T) {
   589  	cases := []struct {
   590  		In       *Resource
   591  		Writable bool
   592  		Err      bool
   593  	}{
   594  		{
   595  			nil,
   596  			true,
   597  			true,
   598  		},
   599  
   600  		// No optional and no required
   601  		{
   602  			&Resource{
   603  				Schema: map[string]*Schema{
   604  					"foo": &Schema{
   605  						Type:     TypeInt,
   606  						Optional: true,
   607  						Required: true,
   608  					},
   609  				},
   610  			},
   611  			true,
   612  			true,
   613  		},
   614  
   615  		// Update undefined for non-ForceNew field
   616  		{
   617  			&Resource{
   618  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   619  				Schema: map[string]*Schema{
   620  					"boo": &Schema{
   621  						Type:     TypeInt,
   622  						Optional: true,
   623  					},
   624  				},
   625  			},
   626  			true,
   627  			true,
   628  		},
   629  
   630  		// Update defined for ForceNew field
   631  		{
   632  			&Resource{
   633  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   634  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   635  				Schema: map[string]*Schema{
   636  					"goo": &Schema{
   637  						Type:     TypeInt,
   638  						Optional: true,
   639  						ForceNew: true,
   640  					},
   641  				},
   642  			},
   643  			true,
   644  			true,
   645  		},
   646  
   647  		// non-writable doesn't need Update, Create or Delete
   648  		{
   649  			&Resource{
   650  				Schema: map[string]*Schema{
   651  					"goo": &Schema{
   652  						Type:     TypeInt,
   653  						Optional: true,
   654  					},
   655  				},
   656  			},
   657  			false,
   658  			false,
   659  		},
   660  
   661  		// non-writable *must not* have Create
   662  		{
   663  			&Resource{
   664  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   665  				Schema: map[string]*Schema{
   666  					"goo": &Schema{
   667  						Type:     TypeInt,
   668  						Optional: true,
   669  					},
   670  				},
   671  			},
   672  			false,
   673  			true,
   674  		},
   675  
   676  		// writable must have Read
   677  		{
   678  			&Resource{
   679  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   680  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   681  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   682  				Schema: map[string]*Schema{
   683  					"goo": &Schema{
   684  						Type:     TypeInt,
   685  						Optional: true,
   686  					},
   687  				},
   688  			},
   689  			true,
   690  			true,
   691  		},
   692  
   693  		// writable must have Delete
   694  		{
   695  			&Resource{
   696  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   697  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   698  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   699  				Schema: map[string]*Schema{
   700  					"goo": &Schema{
   701  						Type:     TypeInt,
   702  						Optional: true,
   703  					},
   704  				},
   705  			},
   706  			true,
   707  			true,
   708  		},
   709  
   710  		8: { // Reserved name at root should be disallowed
   711  			&Resource{
   712  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   713  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   714  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   715  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   716  				Schema: map[string]*Schema{
   717  					"count": {
   718  						Type:     TypeInt,
   719  						Optional: true,
   720  					},
   721  				},
   722  			},
   723  			true,
   724  			true,
   725  		},
   726  
   727  		9: { // Reserved name at nested levels should be allowed
   728  			&Resource{
   729  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   730  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   731  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   732  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   733  				Schema: map[string]*Schema{
   734  					"parent_list": &Schema{
   735  						Type:     TypeString,
   736  						Optional: true,
   737  						Elem: &Resource{
   738  							Schema: map[string]*Schema{
   739  								"provisioner": {
   740  									Type:     TypeString,
   741  									Optional: true,
   742  								},
   743  							},
   744  						},
   745  					},
   746  				},
   747  			},
   748  			true,
   749  			false,
   750  		},
   751  
   752  		10: { // Provider reserved name should be allowed in resource
   753  			&Resource{
   754  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   755  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   756  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   757  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   758  				Schema: map[string]*Schema{
   759  					"alias": &Schema{
   760  						Type:     TypeString,
   761  						Optional: true,
   762  					},
   763  				},
   764  			},
   765  			true,
   766  			false,
   767  		},
   768  	}
   769  
   770  	for i, tc := range cases {
   771  		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   772  			sm := schemaMap{}
   773  			if tc.In != nil {
   774  				sm = schemaMap(tc.In.Schema)
   775  			}
   776  
   777  			err := tc.In.InternalValidate(sm, tc.Writable)
   778  			if err != nil != tc.Err {
   779  				t.Fatalf("%d: bad: %s", i, err)
   780  			}
   781  		})
   782  	}
   783  }
   784  
   785  func TestResourceRefresh(t *testing.T) {
   786  	r := &Resource{
   787  		SchemaVersion: 2,
   788  		Schema: map[string]*Schema{
   789  			"foo": &Schema{
   790  				Type:     TypeInt,
   791  				Optional: true,
   792  			},
   793  		},
   794  	}
   795  
   796  	r.Read = func(d *ResourceData, m interface{}) error {
   797  		if m != 42 {
   798  			return fmt.Errorf("meta not passed")
   799  		}
   800  
   801  		return d.Set("foo", d.Get("foo").(int)+1)
   802  	}
   803  
   804  	s := &terraform.InstanceState{
   805  		ID: "bar",
   806  		Attributes: map[string]string{
   807  			"foo": "12",
   808  		},
   809  	}
   810  
   811  	expected := &terraform.InstanceState{
   812  		ID: "bar",
   813  		Attributes: map[string]string{
   814  			"id":  "bar",
   815  			"foo": "13",
   816  		},
   817  		Meta: map[string]interface{}{
   818  			"schema_version": "2",
   819  		},
   820  	}
   821  
   822  	actual, err := r.Refresh(s, 42)
   823  	if err != nil {
   824  		t.Fatalf("err: %s", err)
   825  	}
   826  
   827  	if !reflect.DeepEqual(actual, expected) {
   828  		t.Fatalf("bad: %#v", actual)
   829  	}
   830  }
   831  
   832  func TestResourceRefresh_blankId(t *testing.T) {
   833  	r := &Resource{
   834  		Schema: map[string]*Schema{
   835  			"foo": &Schema{
   836  				Type:     TypeInt,
   837  				Optional: true,
   838  			},
   839  		},
   840  	}
   841  
   842  	r.Read = func(d *ResourceData, m interface{}) error {
   843  		d.SetId("foo")
   844  		return nil
   845  	}
   846  
   847  	s := &terraform.InstanceState{
   848  		ID:         "",
   849  		Attributes: map[string]string{},
   850  	}
   851  
   852  	actual, err := r.Refresh(s, 42)
   853  	if err != nil {
   854  		t.Fatalf("err: %s", err)
   855  	}
   856  	if actual != nil {
   857  		t.Fatalf("bad: %#v", actual)
   858  	}
   859  }
   860  
   861  func TestResourceRefresh_delete(t *testing.T) {
   862  	r := &Resource{
   863  		Schema: map[string]*Schema{
   864  			"foo": &Schema{
   865  				Type:     TypeInt,
   866  				Optional: true,
   867  			},
   868  		},
   869  	}
   870  
   871  	r.Read = func(d *ResourceData, m interface{}) error {
   872  		d.SetId("")
   873  		return nil
   874  	}
   875  
   876  	s := &terraform.InstanceState{
   877  		ID: "bar",
   878  		Attributes: map[string]string{
   879  			"foo": "12",
   880  		},
   881  	}
   882  
   883  	actual, err := r.Refresh(s, 42)
   884  	if err != nil {
   885  		t.Fatalf("err: %s", err)
   886  	}
   887  
   888  	if actual != nil {
   889  		t.Fatalf("bad: %#v", actual)
   890  	}
   891  }
   892  
   893  func TestResourceRefresh_existsError(t *testing.T) {
   894  	r := &Resource{
   895  		Schema: map[string]*Schema{
   896  			"foo": &Schema{
   897  				Type:     TypeInt,
   898  				Optional: true,
   899  			},
   900  		},
   901  	}
   902  
   903  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   904  		return false, fmt.Errorf("error")
   905  	}
   906  
   907  	r.Read = func(d *ResourceData, m interface{}) error {
   908  		panic("shouldn't be called")
   909  	}
   910  
   911  	s := &terraform.InstanceState{
   912  		ID: "bar",
   913  		Attributes: map[string]string{
   914  			"foo": "12",
   915  		},
   916  	}
   917  
   918  	actual, err := r.Refresh(s, 42)
   919  	if err == nil {
   920  		t.Fatalf("should error")
   921  	}
   922  	if !reflect.DeepEqual(actual, s) {
   923  		t.Fatalf("bad: %#v", actual)
   924  	}
   925  }
   926  
   927  func TestResourceRefresh_noExists(t *testing.T) {
   928  	r := &Resource{
   929  		Schema: map[string]*Schema{
   930  			"foo": &Schema{
   931  				Type:     TypeInt,
   932  				Optional: true,
   933  			},
   934  		},
   935  	}
   936  
   937  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   938  		return false, nil
   939  	}
   940  
   941  	r.Read = func(d *ResourceData, m interface{}) error {
   942  		panic("shouldn't be called")
   943  	}
   944  
   945  	s := &terraform.InstanceState{
   946  		ID: "bar",
   947  		Attributes: map[string]string{
   948  			"foo": "12",
   949  		},
   950  	}
   951  
   952  	actual, err := r.Refresh(s, 42)
   953  	if err != nil {
   954  		t.Fatalf("err: %s", err)
   955  	}
   956  	if actual != nil {
   957  		t.Fatalf("should have no state")
   958  	}
   959  }
   960  
   961  func TestResourceRefresh_needsMigration(t *testing.T) {
   962  	// Schema v2 it deals only in newfoo, which tracks foo as an int
   963  	r := &Resource{
   964  		SchemaVersion: 2,
   965  		Schema: map[string]*Schema{
   966  			"newfoo": &Schema{
   967  				Type:     TypeInt,
   968  				Optional: true,
   969  			},
   970  		},
   971  	}
   972  
   973  	r.Read = func(d *ResourceData, m interface{}) error {
   974  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   975  	}
   976  
   977  	r.MigrateState = func(
   978  		v int,
   979  		s *terraform.InstanceState,
   980  		meta interface{}) (*terraform.InstanceState, error) {
   981  		// Real state migration functions will probably switch on this value,
   982  		// but we'll just assert on it for now.
   983  		if v != 1 {
   984  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
   985  		}
   986  
   987  		if meta != 42 {
   988  			t.Fatal("Expected meta to be passed through to the migration function")
   989  		}
   990  
   991  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
   992  		if err != nil {
   993  			t.Fatalf("err: %#v", err)
   994  		}
   995  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
   996  		delete(s.Attributes, "oldfoo")
   997  
   998  		return s, nil
   999  	}
  1000  
  1001  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
  1002  	// the scale of newfoo
  1003  	s := &terraform.InstanceState{
  1004  		ID: "bar",
  1005  		Attributes: map[string]string{
  1006  			"oldfoo": "1.2",
  1007  		},
  1008  		Meta: map[string]interface{}{
  1009  			"schema_version": "1",
  1010  		},
  1011  	}
  1012  
  1013  	actual, err := r.Refresh(s, 42)
  1014  	if err != nil {
  1015  		t.Fatalf("err: %s", err)
  1016  	}
  1017  
  1018  	expected := &terraform.InstanceState{
  1019  		ID: "bar",
  1020  		Attributes: map[string]string{
  1021  			"id":     "bar",
  1022  			"newfoo": "13",
  1023  		},
  1024  		Meta: map[string]interface{}{
  1025  			"schema_version": "2",
  1026  		},
  1027  	}
  1028  
  1029  	if !reflect.DeepEqual(actual, expected) {
  1030  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1031  	}
  1032  }
  1033  
  1034  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
  1035  	r := &Resource{
  1036  		SchemaVersion: 2,
  1037  		Schema: map[string]*Schema{
  1038  			"newfoo": &Schema{
  1039  				Type:     TypeInt,
  1040  				Optional: true,
  1041  			},
  1042  		},
  1043  	}
  1044  
  1045  	r.Read = func(d *ResourceData, m interface{}) error {
  1046  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1047  	}
  1048  
  1049  	r.MigrateState = func(
  1050  		v int,
  1051  		s *terraform.InstanceState,
  1052  		meta interface{}) (*terraform.InstanceState, error) {
  1053  		t.Fatal("Migrate function shouldn't be called!")
  1054  		return nil, nil
  1055  	}
  1056  
  1057  	s := &terraform.InstanceState{
  1058  		ID: "bar",
  1059  		Attributes: map[string]string{
  1060  			"newfoo": "12",
  1061  		},
  1062  		Meta: map[string]interface{}{
  1063  			"schema_version": "2",
  1064  		},
  1065  	}
  1066  
  1067  	actual, err := r.Refresh(s, nil)
  1068  	if err != nil {
  1069  		t.Fatalf("err: %s", err)
  1070  	}
  1071  
  1072  	expected := &terraform.InstanceState{
  1073  		ID: "bar",
  1074  		Attributes: map[string]string{
  1075  			"id":     "bar",
  1076  			"newfoo": "13",
  1077  		},
  1078  		Meta: map[string]interface{}{
  1079  			"schema_version": "2",
  1080  		},
  1081  	}
  1082  
  1083  	if !reflect.DeepEqual(actual, expected) {
  1084  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1085  	}
  1086  }
  1087  
  1088  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
  1089  	r := &Resource{
  1090  		// Version 1 > Version 0
  1091  		SchemaVersion: 1,
  1092  		Schema: map[string]*Schema{
  1093  			"newfoo": &Schema{
  1094  				Type:     TypeInt,
  1095  				Optional: true,
  1096  			},
  1097  		},
  1098  	}
  1099  
  1100  	r.Read = func(d *ResourceData, m interface{}) error {
  1101  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1102  	}
  1103  
  1104  	r.MigrateState = func(
  1105  		v int,
  1106  		s *terraform.InstanceState,
  1107  		meta interface{}) (*terraform.InstanceState, error) {
  1108  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
  1109  		return s, nil
  1110  	}
  1111  
  1112  	s := &terraform.InstanceState{
  1113  		ID: "bar",
  1114  		Attributes: map[string]string{
  1115  			"oldfoo": "12",
  1116  		},
  1117  	}
  1118  
  1119  	actual, err := r.Refresh(s, nil)
  1120  	if err != nil {
  1121  		t.Fatalf("err: %s", err)
  1122  	}
  1123  
  1124  	expected := &terraform.InstanceState{
  1125  		ID: "bar",
  1126  		Attributes: map[string]string{
  1127  			"id":     "bar",
  1128  			"newfoo": "13",
  1129  		},
  1130  		Meta: map[string]interface{}{
  1131  			"schema_version": "1",
  1132  		},
  1133  	}
  1134  
  1135  	if !reflect.DeepEqual(actual, expected) {
  1136  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1137  	}
  1138  }
  1139  
  1140  func TestResourceRefresh_migrateStateErr(t *testing.T) {
  1141  	r := &Resource{
  1142  		SchemaVersion: 2,
  1143  		Schema: map[string]*Schema{
  1144  			"newfoo": &Schema{
  1145  				Type:     TypeInt,
  1146  				Optional: true,
  1147  			},
  1148  		},
  1149  	}
  1150  
  1151  	r.Read = func(d *ResourceData, m interface{}) error {
  1152  		t.Fatal("Read should never be called!")
  1153  		return nil
  1154  	}
  1155  
  1156  	r.MigrateState = func(
  1157  		v int,
  1158  		s *terraform.InstanceState,
  1159  		meta interface{}) (*terraform.InstanceState, error) {
  1160  		return s, fmt.Errorf("triggering an error")
  1161  	}
  1162  
  1163  	s := &terraform.InstanceState{
  1164  		ID: "bar",
  1165  		Attributes: map[string]string{
  1166  			"oldfoo": "12",
  1167  		},
  1168  	}
  1169  
  1170  	_, err := r.Refresh(s, nil)
  1171  	if err == nil {
  1172  		t.Fatal("expected error, but got none!")
  1173  	}
  1174  }
  1175  
  1176  func TestResourceData(t *testing.T) {
  1177  	r := &Resource{
  1178  		SchemaVersion: 2,
  1179  		Schema: map[string]*Schema{
  1180  			"foo": &Schema{
  1181  				Type:     TypeInt,
  1182  				Optional: true,
  1183  			},
  1184  		},
  1185  	}
  1186  
  1187  	state := &terraform.InstanceState{
  1188  		ID: "foo",
  1189  		Attributes: map[string]string{
  1190  			"id":  "foo",
  1191  			"foo": "42",
  1192  		},
  1193  	}
  1194  
  1195  	data := r.Data(state)
  1196  	if data.Id() != "foo" {
  1197  		t.Fatalf("err: %s", data.Id())
  1198  	}
  1199  	if v := data.Get("foo"); v != 42 {
  1200  		t.Fatalf("bad: %#v", v)
  1201  	}
  1202  
  1203  	// Set expectations
  1204  	state.Meta = map[string]interface{}{
  1205  		"schema_version": "2",
  1206  	}
  1207  
  1208  	result := data.State()
  1209  	if !reflect.DeepEqual(result, state) {
  1210  		t.Fatalf("bad: %#v", result)
  1211  	}
  1212  }
  1213  
  1214  func TestResourceData_blank(t *testing.T) {
  1215  	r := &Resource{
  1216  		SchemaVersion: 2,
  1217  		Schema: map[string]*Schema{
  1218  			"foo": &Schema{
  1219  				Type:     TypeInt,
  1220  				Optional: true,
  1221  			},
  1222  		},
  1223  	}
  1224  
  1225  	data := r.Data(nil)
  1226  	if data.Id() != "" {
  1227  		t.Fatalf("err: %s", data.Id())
  1228  	}
  1229  	if v := data.Get("foo"); v != 0 {
  1230  		t.Fatalf("bad: %#v", v)
  1231  	}
  1232  }