github.com/gerbenjacobs/terraform@v0.9.5-0.20170630130047-e6ddd62583d8/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  
   711  	for i, tc := range cases {
   712  		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   713  			err := tc.In.InternalValidate(schemaMap{}, tc.Writable)
   714  			if err != nil != tc.Err {
   715  				t.Fatalf("%d: bad: %s", i, err)
   716  			}
   717  		})
   718  	}
   719  }
   720  
   721  func TestResourceRefresh(t *testing.T) {
   722  	r := &Resource{
   723  		SchemaVersion: 2,
   724  		Schema: map[string]*Schema{
   725  			"foo": &Schema{
   726  				Type:     TypeInt,
   727  				Optional: true,
   728  			},
   729  		},
   730  	}
   731  
   732  	r.Read = func(d *ResourceData, m interface{}) error {
   733  		if m != 42 {
   734  			return fmt.Errorf("meta not passed")
   735  		}
   736  
   737  		return d.Set("foo", d.Get("foo").(int)+1)
   738  	}
   739  
   740  	s := &terraform.InstanceState{
   741  		ID: "bar",
   742  		Attributes: map[string]string{
   743  			"foo": "12",
   744  		},
   745  	}
   746  
   747  	expected := &terraform.InstanceState{
   748  		ID: "bar",
   749  		Attributes: map[string]string{
   750  			"id":  "bar",
   751  			"foo": "13",
   752  		},
   753  		Meta: map[string]interface{}{
   754  			"schema_version": "2",
   755  		},
   756  	}
   757  
   758  	actual, err := r.Refresh(s, 42)
   759  	if err != nil {
   760  		t.Fatalf("err: %s", err)
   761  	}
   762  
   763  	if !reflect.DeepEqual(actual, expected) {
   764  		t.Fatalf("bad: %#v", actual)
   765  	}
   766  }
   767  
   768  func TestResourceRefresh_blankId(t *testing.T) {
   769  	r := &Resource{
   770  		Schema: map[string]*Schema{
   771  			"foo": &Schema{
   772  				Type:     TypeInt,
   773  				Optional: true,
   774  			},
   775  		},
   776  	}
   777  
   778  	r.Read = func(d *ResourceData, m interface{}) error {
   779  		d.SetId("foo")
   780  		return nil
   781  	}
   782  
   783  	s := &terraform.InstanceState{
   784  		ID:         "",
   785  		Attributes: map[string]string{},
   786  	}
   787  
   788  	actual, err := r.Refresh(s, 42)
   789  	if err != nil {
   790  		t.Fatalf("err: %s", err)
   791  	}
   792  	if actual != nil {
   793  		t.Fatalf("bad: %#v", actual)
   794  	}
   795  }
   796  
   797  func TestResourceRefresh_delete(t *testing.T) {
   798  	r := &Resource{
   799  		Schema: map[string]*Schema{
   800  			"foo": &Schema{
   801  				Type:     TypeInt,
   802  				Optional: true,
   803  			},
   804  		},
   805  	}
   806  
   807  	r.Read = func(d *ResourceData, m interface{}) error {
   808  		d.SetId("")
   809  		return nil
   810  	}
   811  
   812  	s := &terraform.InstanceState{
   813  		ID: "bar",
   814  		Attributes: map[string]string{
   815  			"foo": "12",
   816  		},
   817  	}
   818  
   819  	actual, err := r.Refresh(s, 42)
   820  	if err != nil {
   821  		t.Fatalf("err: %s", err)
   822  	}
   823  
   824  	if actual != nil {
   825  		t.Fatalf("bad: %#v", actual)
   826  	}
   827  }
   828  
   829  func TestResourceRefresh_existsError(t *testing.T) {
   830  	r := &Resource{
   831  		Schema: map[string]*Schema{
   832  			"foo": &Schema{
   833  				Type:     TypeInt,
   834  				Optional: true,
   835  			},
   836  		},
   837  	}
   838  
   839  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   840  		return false, fmt.Errorf("error")
   841  	}
   842  
   843  	r.Read = func(d *ResourceData, m interface{}) error {
   844  		panic("shouldn't be called")
   845  	}
   846  
   847  	s := &terraform.InstanceState{
   848  		ID: "bar",
   849  		Attributes: map[string]string{
   850  			"foo": "12",
   851  		},
   852  	}
   853  
   854  	actual, err := r.Refresh(s, 42)
   855  	if err == nil {
   856  		t.Fatalf("should error")
   857  	}
   858  	if !reflect.DeepEqual(actual, s) {
   859  		t.Fatalf("bad: %#v", actual)
   860  	}
   861  }
   862  
   863  func TestResourceRefresh_noExists(t *testing.T) {
   864  	r := &Resource{
   865  		Schema: map[string]*Schema{
   866  			"foo": &Schema{
   867  				Type:     TypeInt,
   868  				Optional: true,
   869  			},
   870  		},
   871  	}
   872  
   873  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   874  		return false, nil
   875  	}
   876  
   877  	r.Read = func(d *ResourceData, m interface{}) error {
   878  		panic("shouldn't be called")
   879  	}
   880  
   881  	s := &terraform.InstanceState{
   882  		ID: "bar",
   883  		Attributes: map[string]string{
   884  			"foo": "12",
   885  		},
   886  	}
   887  
   888  	actual, err := r.Refresh(s, 42)
   889  	if err != nil {
   890  		t.Fatalf("err: %s", err)
   891  	}
   892  	if actual != nil {
   893  		t.Fatalf("should have no state")
   894  	}
   895  }
   896  
   897  func TestResourceRefresh_needsMigration(t *testing.T) {
   898  	// Schema v2 it deals only in newfoo, which tracks foo as an int
   899  	r := &Resource{
   900  		SchemaVersion: 2,
   901  		Schema: map[string]*Schema{
   902  			"newfoo": &Schema{
   903  				Type:     TypeInt,
   904  				Optional: true,
   905  			},
   906  		},
   907  	}
   908  
   909  	r.Read = func(d *ResourceData, m interface{}) error {
   910  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   911  	}
   912  
   913  	r.MigrateState = func(
   914  		v int,
   915  		s *terraform.InstanceState,
   916  		meta interface{}) (*terraform.InstanceState, error) {
   917  		// Real state migration functions will probably switch on this value,
   918  		// but we'll just assert on it for now.
   919  		if v != 1 {
   920  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
   921  		}
   922  
   923  		if meta != 42 {
   924  			t.Fatal("Expected meta to be passed through to the migration function")
   925  		}
   926  
   927  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
   928  		if err != nil {
   929  			t.Fatalf("err: %#v", err)
   930  		}
   931  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
   932  		delete(s.Attributes, "oldfoo")
   933  
   934  		return s, nil
   935  	}
   936  
   937  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
   938  	// the scale of newfoo
   939  	s := &terraform.InstanceState{
   940  		ID: "bar",
   941  		Attributes: map[string]string{
   942  			"oldfoo": "1.2",
   943  		},
   944  		Meta: map[string]interface{}{
   945  			"schema_version": "1",
   946  		},
   947  	}
   948  
   949  	actual, err := r.Refresh(s, 42)
   950  	if err != nil {
   951  		t.Fatalf("err: %s", err)
   952  	}
   953  
   954  	expected := &terraform.InstanceState{
   955  		ID: "bar",
   956  		Attributes: map[string]string{
   957  			"id":     "bar",
   958  			"newfoo": "13",
   959  		},
   960  		Meta: map[string]interface{}{
   961  			"schema_version": "2",
   962  		},
   963  	}
   964  
   965  	if !reflect.DeepEqual(actual, expected) {
   966  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   967  	}
   968  }
   969  
   970  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
   971  	r := &Resource{
   972  		SchemaVersion: 2,
   973  		Schema: map[string]*Schema{
   974  			"newfoo": &Schema{
   975  				Type:     TypeInt,
   976  				Optional: true,
   977  			},
   978  		},
   979  	}
   980  
   981  	r.Read = func(d *ResourceData, m interface{}) error {
   982  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   983  	}
   984  
   985  	r.MigrateState = func(
   986  		v int,
   987  		s *terraform.InstanceState,
   988  		meta interface{}) (*terraform.InstanceState, error) {
   989  		t.Fatal("Migrate function shouldn't be called!")
   990  		return nil, nil
   991  	}
   992  
   993  	s := &terraform.InstanceState{
   994  		ID: "bar",
   995  		Attributes: map[string]string{
   996  			"newfoo": "12",
   997  		},
   998  		Meta: map[string]interface{}{
   999  			"schema_version": "2",
  1000  		},
  1001  	}
  1002  
  1003  	actual, err := r.Refresh(s, nil)
  1004  	if err != nil {
  1005  		t.Fatalf("err: %s", err)
  1006  	}
  1007  
  1008  	expected := &terraform.InstanceState{
  1009  		ID: "bar",
  1010  		Attributes: map[string]string{
  1011  			"id":     "bar",
  1012  			"newfoo": "13",
  1013  		},
  1014  		Meta: map[string]interface{}{
  1015  			"schema_version": "2",
  1016  		},
  1017  	}
  1018  
  1019  	if !reflect.DeepEqual(actual, expected) {
  1020  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1021  	}
  1022  }
  1023  
  1024  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
  1025  	r := &Resource{
  1026  		// Version 1 > Version 0
  1027  		SchemaVersion: 1,
  1028  		Schema: map[string]*Schema{
  1029  			"newfoo": &Schema{
  1030  				Type:     TypeInt,
  1031  				Optional: true,
  1032  			},
  1033  		},
  1034  	}
  1035  
  1036  	r.Read = func(d *ResourceData, m interface{}) error {
  1037  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1038  	}
  1039  
  1040  	r.MigrateState = func(
  1041  		v int,
  1042  		s *terraform.InstanceState,
  1043  		meta interface{}) (*terraform.InstanceState, error) {
  1044  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
  1045  		return s, nil
  1046  	}
  1047  
  1048  	s := &terraform.InstanceState{
  1049  		ID: "bar",
  1050  		Attributes: map[string]string{
  1051  			"oldfoo": "12",
  1052  		},
  1053  	}
  1054  
  1055  	actual, err := r.Refresh(s, nil)
  1056  	if err != nil {
  1057  		t.Fatalf("err: %s", err)
  1058  	}
  1059  
  1060  	expected := &terraform.InstanceState{
  1061  		ID: "bar",
  1062  		Attributes: map[string]string{
  1063  			"id":     "bar",
  1064  			"newfoo": "13",
  1065  		},
  1066  		Meta: map[string]interface{}{
  1067  			"schema_version": "1",
  1068  		},
  1069  	}
  1070  
  1071  	if !reflect.DeepEqual(actual, expected) {
  1072  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1073  	}
  1074  }
  1075  
  1076  func TestResourceRefresh_migrateStateErr(t *testing.T) {
  1077  	r := &Resource{
  1078  		SchemaVersion: 2,
  1079  		Schema: map[string]*Schema{
  1080  			"newfoo": &Schema{
  1081  				Type:     TypeInt,
  1082  				Optional: true,
  1083  			},
  1084  		},
  1085  	}
  1086  
  1087  	r.Read = func(d *ResourceData, m interface{}) error {
  1088  		t.Fatal("Read should never be called!")
  1089  		return nil
  1090  	}
  1091  
  1092  	r.MigrateState = func(
  1093  		v int,
  1094  		s *terraform.InstanceState,
  1095  		meta interface{}) (*terraform.InstanceState, error) {
  1096  		return s, fmt.Errorf("triggering an error")
  1097  	}
  1098  
  1099  	s := &terraform.InstanceState{
  1100  		ID: "bar",
  1101  		Attributes: map[string]string{
  1102  			"oldfoo": "12",
  1103  		},
  1104  	}
  1105  
  1106  	_, err := r.Refresh(s, nil)
  1107  	if err == nil {
  1108  		t.Fatal("expected error, but got none!")
  1109  	}
  1110  }
  1111  
  1112  func TestResourceData(t *testing.T) {
  1113  	r := &Resource{
  1114  		SchemaVersion: 2,
  1115  		Schema: map[string]*Schema{
  1116  			"foo": &Schema{
  1117  				Type:     TypeInt,
  1118  				Optional: true,
  1119  			},
  1120  		},
  1121  	}
  1122  
  1123  	state := &terraform.InstanceState{
  1124  		ID: "foo",
  1125  		Attributes: map[string]string{
  1126  			"id":  "foo",
  1127  			"foo": "42",
  1128  		},
  1129  	}
  1130  
  1131  	data := r.Data(state)
  1132  	if data.Id() != "foo" {
  1133  		t.Fatalf("err: %s", data.Id())
  1134  	}
  1135  	if v := data.Get("foo"); v != 42 {
  1136  		t.Fatalf("bad: %#v", v)
  1137  	}
  1138  
  1139  	// Set expectations
  1140  	state.Meta = map[string]interface{}{
  1141  		"schema_version": "2",
  1142  	}
  1143  
  1144  	result := data.State()
  1145  	if !reflect.DeepEqual(result, state) {
  1146  		t.Fatalf("bad: %#v", result)
  1147  	}
  1148  }
  1149  
  1150  func TestResourceData_blank(t *testing.T) {
  1151  	r := &Resource{
  1152  		SchemaVersion: 2,
  1153  		Schema: map[string]*Schema{
  1154  			"foo": &Schema{
  1155  				Type:     TypeInt,
  1156  				Optional: true,
  1157  			},
  1158  		},
  1159  	}
  1160  
  1161  	data := r.Data(nil)
  1162  	if data.Id() != "" {
  1163  		t.Fatalf("err: %s", data.Id())
  1164  	}
  1165  	if v := data.Get("foo"); v != 0 {
  1166  		t.Fatalf("bad: %#v", v)
  1167  	}
  1168  }