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