github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/resource_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package schema
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"reflect"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  	"github.com/opentofu/opentofu/internal/configs/hcl2shim"
    18  	"github.com/opentofu/opentofu/internal/legacy/tofu"
    19  
    20  	"github.com/zclconf/go-cty/cty"
    21  	ctyjson "github.com/zclconf/go-cty/cty/json"
    22  )
    23  
    24  func TestResourceApply_create(t *testing.T) {
    25  	r := &Resource{
    26  		SchemaVersion: 2,
    27  		Schema: map[string]*Schema{
    28  			"foo": &Schema{
    29  				Type:     TypeInt,
    30  				Optional: true,
    31  			},
    32  		},
    33  	}
    34  
    35  	called := false
    36  	r.Create = func(d *ResourceData, m interface{}) error {
    37  		called = true
    38  		d.SetId("foo")
    39  		return nil
    40  	}
    41  
    42  	var s *tofu.InstanceState = nil
    43  
    44  	d := &tofu.InstanceDiff{
    45  		Attributes: map[string]*tofu.ResourceAttrDiff{
    46  			"foo": &tofu.ResourceAttrDiff{
    47  				New: "42",
    48  			},
    49  		},
    50  	}
    51  
    52  	actual, err := r.Apply(s, d, nil)
    53  	if err != nil {
    54  		t.Fatalf("err: %s", err)
    55  	}
    56  
    57  	if !called {
    58  		t.Fatal("not called")
    59  	}
    60  
    61  	expected := &tofu.InstanceState{
    62  		ID: "foo",
    63  		Attributes: map[string]string{
    64  			"id":  "foo",
    65  			"foo": "42",
    66  		},
    67  		Meta: map[string]interface{}{
    68  			"schema_version": "2",
    69  		},
    70  	}
    71  
    72  	if !reflect.DeepEqual(actual, expected) {
    73  		t.Fatalf("bad: %#v", actual)
    74  	}
    75  }
    76  
    77  func TestResourceApply_Timeout_state(t *testing.T) {
    78  	r := &Resource{
    79  		SchemaVersion: 2,
    80  		Schema: map[string]*Schema{
    81  			"foo": &Schema{
    82  				Type:     TypeInt,
    83  				Optional: true,
    84  			},
    85  		},
    86  		Timeouts: &ResourceTimeout{
    87  			Create: DefaultTimeout(40 * time.Minute),
    88  			Update: DefaultTimeout(80 * time.Minute),
    89  			Delete: DefaultTimeout(40 * time.Minute),
    90  		},
    91  	}
    92  
    93  	called := false
    94  	r.Create = func(d *ResourceData, m interface{}) error {
    95  		called = true
    96  		d.SetId("foo")
    97  		return nil
    98  	}
    99  
   100  	var s *tofu.InstanceState = nil
   101  
   102  	d := &tofu.InstanceDiff{
   103  		Attributes: map[string]*tofu.ResourceAttrDiff{
   104  			"foo": &tofu.ResourceAttrDiff{
   105  				New: "42",
   106  			},
   107  		},
   108  	}
   109  
   110  	diffTimeout := &ResourceTimeout{
   111  		Create: DefaultTimeout(40 * time.Minute),
   112  		Update: DefaultTimeout(80 * time.Minute),
   113  		Delete: DefaultTimeout(40 * time.Minute),
   114  	}
   115  
   116  	if err := diffTimeout.DiffEncode(d); err != nil {
   117  		t.Fatalf("Error encoding timeout to diff: %s", err)
   118  	}
   119  
   120  	actual, err := r.Apply(s, d, nil)
   121  	if err != nil {
   122  		t.Fatalf("err: %s", err)
   123  	}
   124  
   125  	if !called {
   126  		t.Fatal("not called")
   127  	}
   128  
   129  	expected := &tofu.InstanceState{
   130  		ID: "foo",
   131  		Attributes: map[string]string{
   132  			"id":  "foo",
   133  			"foo": "42",
   134  		},
   135  		Meta: map[string]interface{}{
   136  			"schema_version": "2",
   137  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   138  		},
   139  	}
   140  
   141  	if !reflect.DeepEqual(actual, expected) {
   142  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   143  	}
   144  }
   145  
   146  // Regression test to ensure that the meta data is read from state, if a
   147  // resource is destroyed and the timeout meta is no longer available from the
   148  // config
   149  func TestResourceApply_Timeout_destroy(t *testing.T) {
   150  	timeouts := &ResourceTimeout{
   151  		Create: DefaultTimeout(40 * time.Minute),
   152  		Update: DefaultTimeout(80 * time.Minute),
   153  		Delete: DefaultTimeout(40 * time.Minute),
   154  	}
   155  
   156  	r := &Resource{
   157  		Schema: map[string]*Schema{
   158  			"foo": &Schema{
   159  				Type:     TypeInt,
   160  				Optional: true,
   161  			},
   162  		},
   163  		Timeouts: timeouts,
   164  	}
   165  
   166  	called := false
   167  	var delTimeout time.Duration
   168  	r.Delete = func(d *ResourceData, m interface{}) error {
   169  		delTimeout = d.Timeout(TimeoutDelete)
   170  		called = true
   171  		return nil
   172  	}
   173  
   174  	s := &tofu.InstanceState{
   175  		ID: "bar",
   176  	}
   177  
   178  	if err := timeouts.StateEncode(s); err != nil {
   179  		t.Fatalf("Error encoding to state: %s", err)
   180  	}
   181  
   182  	d := &tofu.InstanceDiff{
   183  		Destroy: true,
   184  	}
   185  
   186  	actual, err := r.Apply(s, d, nil)
   187  	if err != nil {
   188  		t.Fatalf("err: %s", err)
   189  	}
   190  
   191  	if !called {
   192  		t.Fatal("delete not called")
   193  	}
   194  
   195  	if *timeouts.Delete != delTimeout {
   196  		t.Fatalf("timeouts don't match, expected (%#v), got (%#v)", timeouts.Delete, delTimeout)
   197  	}
   198  
   199  	if actual != nil {
   200  		t.Fatalf("bad: %#v", actual)
   201  	}
   202  }
   203  
   204  func TestResourceDiff_Timeout_diff(t *testing.T) {
   205  	r := &Resource{
   206  		Schema: map[string]*Schema{
   207  			"foo": &Schema{
   208  				Type:     TypeInt,
   209  				Optional: true,
   210  			},
   211  		},
   212  		Timeouts: &ResourceTimeout{
   213  			Create: DefaultTimeout(40 * time.Minute),
   214  			Update: DefaultTimeout(80 * time.Minute),
   215  			Delete: DefaultTimeout(40 * time.Minute),
   216  		},
   217  	}
   218  
   219  	r.Create = func(d *ResourceData, m interface{}) error {
   220  		d.SetId("foo")
   221  		return nil
   222  	}
   223  
   224  	conf := tofu.NewResourceConfigRaw(
   225  		map[string]interface{}{
   226  			"foo": 42,
   227  			TimeoutsConfigKey: map[string]interface{}{
   228  				"create": "2h",
   229  			},
   230  		},
   231  	)
   232  	var s *tofu.InstanceState
   233  
   234  	actual, err := r.Diff(s, conf, nil)
   235  	if err != nil {
   236  		t.Fatalf("err: %s", err)
   237  	}
   238  
   239  	expected := &tofu.InstanceDiff{
   240  		Attributes: map[string]*tofu.ResourceAttrDiff{
   241  			"foo": &tofu.ResourceAttrDiff{
   242  				New: "42",
   243  			},
   244  		},
   245  	}
   246  
   247  	diffTimeout := &ResourceTimeout{
   248  		Create: DefaultTimeout(120 * time.Minute),
   249  		Update: DefaultTimeout(80 * time.Minute),
   250  		Delete: DefaultTimeout(40 * time.Minute),
   251  	}
   252  
   253  	if err := diffTimeout.DiffEncode(expected); err != nil {
   254  		t.Fatalf("Error encoding timeout to diff: %s", err)
   255  	}
   256  
   257  	if !reflect.DeepEqual(actual, expected) {
   258  		t.Fatalf("Not equal Meta in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   259  	}
   260  }
   261  
   262  func TestResourceDiff_CustomizeFunc(t *testing.T) {
   263  	r := &Resource{
   264  		Schema: map[string]*Schema{
   265  			"foo": &Schema{
   266  				Type:     TypeInt,
   267  				Optional: true,
   268  			},
   269  		},
   270  	}
   271  
   272  	var called bool
   273  
   274  	r.CustomizeDiff = func(d *ResourceDiff, m interface{}) error {
   275  		called = true
   276  		return nil
   277  	}
   278  
   279  	conf := tofu.NewResourceConfigRaw(
   280  		map[string]interface{}{
   281  			"foo": 42,
   282  		},
   283  	)
   284  
   285  	var s *tofu.InstanceState
   286  
   287  	_, err := r.Diff(s, conf, nil)
   288  	if err != nil {
   289  		t.Fatalf("err: %s", err)
   290  	}
   291  
   292  	if !called {
   293  		t.Fatalf("diff customization not called")
   294  	}
   295  }
   296  
   297  func TestResourceApply_destroy(t *testing.T) {
   298  	r := &Resource{
   299  		Schema: map[string]*Schema{
   300  			"foo": &Schema{
   301  				Type:     TypeInt,
   302  				Optional: true,
   303  			},
   304  		},
   305  	}
   306  
   307  	called := false
   308  	r.Delete = func(d *ResourceData, m interface{}) error {
   309  		called = true
   310  		return nil
   311  	}
   312  
   313  	s := &tofu.InstanceState{
   314  		ID: "bar",
   315  	}
   316  
   317  	d := &tofu.InstanceDiff{
   318  		Destroy: true,
   319  	}
   320  
   321  	actual, err := r.Apply(s, d, nil)
   322  	if err != nil {
   323  		t.Fatalf("err: %s", err)
   324  	}
   325  
   326  	if !called {
   327  		t.Fatal("delete not called")
   328  	}
   329  
   330  	if actual != nil {
   331  		t.Fatalf("bad: %#v", actual)
   332  	}
   333  }
   334  
   335  func TestResourceApply_destroyCreate(t *testing.T) {
   336  	r := &Resource{
   337  		Schema: map[string]*Schema{
   338  			"foo": &Schema{
   339  				Type:     TypeInt,
   340  				Optional: true,
   341  			},
   342  
   343  			"tags": &Schema{
   344  				Type:     TypeMap,
   345  				Optional: true,
   346  				Computed: true,
   347  			},
   348  		},
   349  	}
   350  
   351  	change := false
   352  	r.Create = func(d *ResourceData, m interface{}) error {
   353  		change = d.HasChange("tags")
   354  		d.SetId("foo")
   355  		return nil
   356  	}
   357  	r.Delete = func(d *ResourceData, m interface{}) error {
   358  		return nil
   359  	}
   360  
   361  	var s *tofu.InstanceState = &tofu.InstanceState{
   362  		ID: "bar",
   363  		Attributes: map[string]string{
   364  			"foo":       "bar",
   365  			"tags.Name": "foo",
   366  		},
   367  	}
   368  
   369  	d := &tofu.InstanceDiff{
   370  		Attributes: map[string]*tofu.ResourceAttrDiff{
   371  			"foo": &tofu.ResourceAttrDiff{
   372  				New:         "42",
   373  				RequiresNew: true,
   374  			},
   375  			"tags.Name": &tofu.ResourceAttrDiff{
   376  				Old:         "foo",
   377  				New:         "foo",
   378  				RequiresNew: true,
   379  			},
   380  		},
   381  	}
   382  
   383  	actual, err := r.Apply(s, d, nil)
   384  	if err != nil {
   385  		t.Fatalf("err: %s", err)
   386  	}
   387  
   388  	if !change {
   389  		t.Fatal("should have change")
   390  	}
   391  
   392  	expected := &tofu.InstanceState{
   393  		ID: "foo",
   394  		Attributes: map[string]string{
   395  			"id":        "foo",
   396  			"foo":       "42",
   397  			"tags.%":    "1",
   398  			"tags.Name": "foo",
   399  		},
   400  	}
   401  
   402  	if !reflect.DeepEqual(actual, expected) {
   403  		t.Fatalf("bad: %#v", actual)
   404  	}
   405  }
   406  
   407  func TestResourceApply_destroyPartial(t *testing.T) {
   408  	r := &Resource{
   409  		Schema: map[string]*Schema{
   410  			"foo": &Schema{
   411  				Type:     TypeInt,
   412  				Optional: true,
   413  			},
   414  		},
   415  		SchemaVersion: 3,
   416  	}
   417  
   418  	r.Delete = func(d *ResourceData, m interface{}) error {
   419  		d.Set("foo", 42)
   420  		return fmt.Errorf("some error")
   421  	}
   422  
   423  	s := &tofu.InstanceState{
   424  		ID: "bar",
   425  		Attributes: map[string]string{
   426  			"foo": "12",
   427  		},
   428  	}
   429  
   430  	d := &tofu.InstanceDiff{
   431  		Destroy: true,
   432  	}
   433  
   434  	actual, err := r.Apply(s, d, nil)
   435  	if err == nil {
   436  		t.Fatal("should error")
   437  	}
   438  
   439  	expected := &tofu.InstanceState{
   440  		ID: "bar",
   441  		Attributes: map[string]string{
   442  			"id":  "bar",
   443  			"foo": "42",
   444  		},
   445  		Meta: map[string]interface{}{
   446  			"schema_version": "3",
   447  		},
   448  	}
   449  
   450  	if !reflect.DeepEqual(actual, expected) {
   451  		t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual)
   452  	}
   453  }
   454  
   455  func TestResourceApply_update(t *testing.T) {
   456  	r := &Resource{
   457  		Schema: map[string]*Schema{
   458  			"foo": &Schema{
   459  				Type:     TypeInt,
   460  				Optional: true,
   461  			},
   462  		},
   463  	}
   464  
   465  	r.Update = func(d *ResourceData, m interface{}) error {
   466  		d.Set("foo", 42)
   467  		return nil
   468  	}
   469  
   470  	s := &tofu.InstanceState{
   471  		ID: "foo",
   472  		Attributes: map[string]string{
   473  			"foo": "12",
   474  		},
   475  	}
   476  
   477  	d := &tofu.InstanceDiff{
   478  		Attributes: map[string]*tofu.ResourceAttrDiff{
   479  			"foo": &tofu.ResourceAttrDiff{
   480  				New: "13",
   481  			},
   482  		},
   483  	}
   484  
   485  	actual, err := r.Apply(s, d, nil)
   486  	if err != nil {
   487  		t.Fatalf("err: %s", err)
   488  	}
   489  
   490  	expected := &tofu.InstanceState{
   491  		ID: "foo",
   492  		Attributes: map[string]string{
   493  			"id":  "foo",
   494  			"foo": "42",
   495  		},
   496  	}
   497  
   498  	if !reflect.DeepEqual(actual, expected) {
   499  		t.Fatalf("bad: %#v", actual)
   500  	}
   501  }
   502  
   503  func TestResourceApply_updateNoCallback(t *testing.T) {
   504  	r := &Resource{
   505  		Schema: map[string]*Schema{
   506  			"foo": &Schema{
   507  				Type:     TypeInt,
   508  				Optional: true,
   509  			},
   510  		},
   511  	}
   512  
   513  	r.Update = nil
   514  
   515  	s := &tofu.InstanceState{
   516  		ID: "foo",
   517  		Attributes: map[string]string{
   518  			"foo": "12",
   519  		},
   520  	}
   521  
   522  	d := &tofu.InstanceDiff{
   523  		Attributes: map[string]*tofu.ResourceAttrDiff{
   524  			"foo": &tofu.ResourceAttrDiff{
   525  				New: "13",
   526  			},
   527  		},
   528  	}
   529  
   530  	actual, err := r.Apply(s, d, nil)
   531  	if err == nil {
   532  		t.Fatal("should error")
   533  	}
   534  
   535  	expected := &tofu.InstanceState{
   536  		ID: "foo",
   537  		Attributes: map[string]string{
   538  			"foo": "12",
   539  		},
   540  	}
   541  
   542  	if !reflect.DeepEqual(actual, expected) {
   543  		t.Fatalf("bad: %#v", actual)
   544  	}
   545  }
   546  
   547  func TestResourceApply_isNewResource(t *testing.T) {
   548  	r := &Resource{
   549  		Schema: map[string]*Schema{
   550  			"foo": &Schema{
   551  				Type:     TypeString,
   552  				Optional: true,
   553  			},
   554  		},
   555  	}
   556  
   557  	updateFunc := func(d *ResourceData, m interface{}) error {
   558  		d.Set("foo", "updated")
   559  		if d.IsNewResource() {
   560  			d.Set("foo", "new-resource")
   561  		}
   562  		return nil
   563  	}
   564  	r.Create = func(d *ResourceData, m interface{}) error {
   565  		d.SetId("foo")
   566  		d.Set("foo", "created")
   567  		return updateFunc(d, m)
   568  	}
   569  	r.Update = updateFunc
   570  
   571  	d := &tofu.InstanceDiff{
   572  		Attributes: map[string]*tofu.ResourceAttrDiff{
   573  			"foo": &tofu.ResourceAttrDiff{
   574  				New: "bla-blah",
   575  			},
   576  		},
   577  	}
   578  
   579  	// positive test
   580  	var s *tofu.InstanceState = nil
   581  
   582  	actual, err := r.Apply(s, d, nil)
   583  	if err != nil {
   584  		t.Fatalf("err: %s", err)
   585  	}
   586  
   587  	expected := &tofu.InstanceState{
   588  		ID: "foo",
   589  		Attributes: map[string]string{
   590  			"id":  "foo",
   591  			"foo": "new-resource",
   592  		},
   593  	}
   594  
   595  	if !reflect.DeepEqual(actual, expected) {
   596  		t.Fatalf("actual: %#v\nexpected: %#v",
   597  			actual, expected)
   598  	}
   599  
   600  	// negative test
   601  	s = &tofu.InstanceState{
   602  		ID: "foo",
   603  		Attributes: map[string]string{
   604  			"id":  "foo",
   605  			"foo": "new-resource",
   606  		},
   607  	}
   608  
   609  	actual, err = r.Apply(s, d, nil)
   610  	if err != nil {
   611  		t.Fatalf("err: %s", err)
   612  	}
   613  
   614  	expected = &tofu.InstanceState{
   615  		ID: "foo",
   616  		Attributes: map[string]string{
   617  			"id":  "foo",
   618  			"foo": "updated",
   619  		},
   620  	}
   621  
   622  	if !reflect.DeepEqual(actual, expected) {
   623  		t.Fatalf("actual: %#v\nexpected: %#v",
   624  			actual, expected)
   625  	}
   626  }
   627  
   628  func TestResourceInternalValidate(t *testing.T) {
   629  	cases := []struct {
   630  		In       *Resource
   631  		Writable bool
   632  		Err      bool
   633  	}{
   634  		0: {
   635  			nil,
   636  			true,
   637  			true,
   638  		},
   639  
   640  		// No optional and no required
   641  		1: {
   642  			&Resource{
   643  				Schema: map[string]*Schema{
   644  					"foo": &Schema{
   645  						Type:     TypeInt,
   646  						Optional: true,
   647  						Required: true,
   648  					},
   649  				},
   650  			},
   651  			true,
   652  			true,
   653  		},
   654  
   655  		// Update undefined for non-ForceNew field
   656  		2: {
   657  			&Resource{
   658  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   659  				Schema: map[string]*Schema{
   660  					"boo": &Schema{
   661  						Type:     TypeInt,
   662  						Optional: true,
   663  					},
   664  				},
   665  			},
   666  			true,
   667  			true,
   668  		},
   669  
   670  		// Update defined for ForceNew field
   671  		3: {
   672  			&Resource{
   673  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   674  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   675  				Schema: map[string]*Schema{
   676  					"goo": &Schema{
   677  						Type:     TypeInt,
   678  						Optional: true,
   679  						ForceNew: true,
   680  					},
   681  				},
   682  			},
   683  			true,
   684  			true,
   685  		},
   686  
   687  		// non-writable doesn't need Update, Create or Delete
   688  		4: {
   689  			&Resource{
   690  				Schema: map[string]*Schema{
   691  					"goo": &Schema{
   692  						Type:     TypeInt,
   693  						Optional: true,
   694  					},
   695  				},
   696  			},
   697  			false,
   698  			false,
   699  		},
   700  
   701  		// non-writable *must not* have Create
   702  		5: {
   703  			&Resource{
   704  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   705  				Schema: map[string]*Schema{
   706  					"goo": &Schema{
   707  						Type:     TypeInt,
   708  						Optional: true,
   709  					},
   710  				},
   711  			},
   712  			false,
   713  			true,
   714  		},
   715  
   716  		// writable must have Read
   717  		6: {
   718  			&Resource{
   719  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   720  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   721  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   722  				Schema: map[string]*Schema{
   723  					"goo": &Schema{
   724  						Type:     TypeInt,
   725  						Optional: true,
   726  					},
   727  				},
   728  			},
   729  			true,
   730  			true,
   731  		},
   732  
   733  		// writable must have Delete
   734  		7: {
   735  			&Resource{
   736  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   737  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   738  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   739  				Schema: map[string]*Schema{
   740  					"goo": &Schema{
   741  						Type:     TypeInt,
   742  						Optional: true,
   743  					},
   744  				},
   745  			},
   746  			true,
   747  			true,
   748  		},
   749  
   750  		8: { // Reserved name at root should be disallowed
   751  			&Resource{
   752  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   753  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   754  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   755  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   756  				Schema: map[string]*Schema{
   757  					"count": {
   758  						Type:     TypeInt,
   759  						Optional: true,
   760  					},
   761  				},
   762  			},
   763  			true,
   764  			true,
   765  		},
   766  
   767  		9: { // Reserved name at nested levels should be allowed
   768  			&Resource{
   769  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   770  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   771  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   772  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   773  				Schema: map[string]*Schema{
   774  					"parent_list": &Schema{
   775  						Type:     TypeString,
   776  						Optional: true,
   777  						Elem: &Resource{
   778  							Schema: map[string]*Schema{
   779  								"provisioner": {
   780  									Type:     TypeString,
   781  									Optional: true,
   782  								},
   783  							},
   784  						},
   785  					},
   786  				},
   787  			},
   788  			true,
   789  			false,
   790  		},
   791  
   792  		10: { // Provider reserved name should be allowed in resource
   793  			&Resource{
   794  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   795  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   796  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   797  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   798  				Schema: map[string]*Schema{
   799  					"alias": &Schema{
   800  						Type:     TypeString,
   801  						Optional: true,
   802  					},
   803  				},
   804  			},
   805  			true,
   806  			false,
   807  		},
   808  
   809  		11: { // ID should be allowed in data source
   810  			&Resource{
   811  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   812  				Schema: map[string]*Schema{
   813  					"id": &Schema{
   814  						Type:     TypeString,
   815  						Optional: true,
   816  					},
   817  				},
   818  			},
   819  			false,
   820  			false,
   821  		},
   822  
   823  		12: { // Deprecated ID should be allowed in resource
   824  			&Resource{
   825  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   826  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   827  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   828  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   829  				Schema: map[string]*Schema{
   830  					"id": &Schema{
   831  						Type:       TypeString,
   832  						Optional:   true,
   833  						Deprecated: "Use x_id instead",
   834  					},
   835  				},
   836  			},
   837  			true,
   838  			false,
   839  		},
   840  
   841  		13: { // non-writable must not define CustomizeDiff
   842  			&Resource{
   843  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   844  				Schema: map[string]*Schema{
   845  					"goo": &Schema{
   846  						Type:     TypeInt,
   847  						Optional: true,
   848  					},
   849  				},
   850  				CustomizeDiff: func(*ResourceDiff, interface{}) error { return nil },
   851  			},
   852  			false,
   853  			true,
   854  		},
   855  		14: { // Deprecated resource
   856  			&Resource{
   857  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   858  				Schema: map[string]*Schema{
   859  					"goo": &Schema{
   860  						Type:     TypeInt,
   861  						Optional: true,
   862  					},
   863  				},
   864  				DeprecationMessage: "This resource has been deprecated.",
   865  			},
   866  			true,
   867  			true,
   868  		},
   869  	}
   870  
   871  	for i, tc := range cases {
   872  		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   873  			sm := schemaMap{}
   874  			if tc.In != nil {
   875  				sm = schemaMap(tc.In.Schema)
   876  			}
   877  
   878  			err := tc.In.InternalValidate(sm, tc.Writable)
   879  			if err != nil && !tc.Err {
   880  				t.Fatalf("%d: expected validation to pass: %s", i, err)
   881  			}
   882  			if err == nil && tc.Err {
   883  				t.Fatalf("%d: expected validation to fail", i)
   884  			}
   885  		})
   886  	}
   887  }
   888  
   889  func TestResourceRefresh(t *testing.T) {
   890  	r := &Resource{
   891  		SchemaVersion: 2,
   892  		Schema: map[string]*Schema{
   893  			"foo": &Schema{
   894  				Type:     TypeInt,
   895  				Optional: true,
   896  			},
   897  		},
   898  	}
   899  
   900  	r.Read = func(d *ResourceData, m interface{}) error {
   901  		if m != 42 {
   902  			return fmt.Errorf("meta not passed")
   903  		}
   904  
   905  		return d.Set("foo", d.Get("foo").(int)+1)
   906  	}
   907  
   908  	s := &tofu.InstanceState{
   909  		ID: "bar",
   910  		Attributes: map[string]string{
   911  			"foo": "12",
   912  		},
   913  	}
   914  
   915  	expected := &tofu.InstanceState{
   916  		ID: "bar",
   917  		Attributes: map[string]string{
   918  			"id":  "bar",
   919  			"foo": "13",
   920  		},
   921  		Meta: map[string]interface{}{
   922  			"schema_version": "2",
   923  		},
   924  	}
   925  
   926  	actual, err := r.Refresh(s, 42)
   927  	if err != nil {
   928  		t.Fatalf("err: %s", err)
   929  	}
   930  
   931  	if !reflect.DeepEqual(actual, expected) {
   932  		t.Fatalf("bad: %#v", actual)
   933  	}
   934  }
   935  
   936  func TestResourceRefresh_blankId(t *testing.T) {
   937  	r := &Resource{
   938  		Schema: map[string]*Schema{
   939  			"foo": &Schema{
   940  				Type:     TypeInt,
   941  				Optional: true,
   942  			},
   943  		},
   944  	}
   945  
   946  	r.Read = func(d *ResourceData, m interface{}) error {
   947  		d.SetId("foo")
   948  		return nil
   949  	}
   950  
   951  	s := &tofu.InstanceState{
   952  		ID:         "",
   953  		Attributes: map[string]string{},
   954  	}
   955  
   956  	actual, err := r.Refresh(s, 42)
   957  	if err != nil {
   958  		t.Fatalf("err: %s", err)
   959  	}
   960  	if actual != nil {
   961  		t.Fatalf("bad: %#v", actual)
   962  	}
   963  }
   964  
   965  func TestResourceRefresh_delete(t *testing.T) {
   966  	r := &Resource{
   967  		Schema: map[string]*Schema{
   968  			"foo": &Schema{
   969  				Type:     TypeInt,
   970  				Optional: true,
   971  			},
   972  		},
   973  	}
   974  
   975  	r.Read = func(d *ResourceData, m interface{}) error {
   976  		d.SetId("")
   977  		return nil
   978  	}
   979  
   980  	s := &tofu.InstanceState{
   981  		ID: "bar",
   982  		Attributes: map[string]string{
   983  			"foo": "12",
   984  		},
   985  	}
   986  
   987  	actual, err := r.Refresh(s, 42)
   988  	if err != nil {
   989  		t.Fatalf("err: %s", err)
   990  	}
   991  
   992  	if actual != nil {
   993  		t.Fatalf("bad: %#v", actual)
   994  	}
   995  }
   996  
   997  func TestResourceRefresh_existsError(t *testing.T) {
   998  	r := &Resource{
   999  		Schema: map[string]*Schema{
  1000  			"foo": &Schema{
  1001  				Type:     TypeInt,
  1002  				Optional: true,
  1003  			},
  1004  		},
  1005  	}
  1006  
  1007  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
  1008  		return false, fmt.Errorf("error")
  1009  	}
  1010  
  1011  	r.Read = func(d *ResourceData, m interface{}) error {
  1012  		panic("shouldn't be called")
  1013  	}
  1014  
  1015  	s := &tofu.InstanceState{
  1016  		ID: "bar",
  1017  		Attributes: map[string]string{
  1018  			"foo": "12",
  1019  		},
  1020  	}
  1021  
  1022  	actual, err := r.Refresh(s, 42)
  1023  	if err == nil {
  1024  		t.Fatalf("should error")
  1025  	}
  1026  	if !reflect.DeepEqual(actual, s) {
  1027  		t.Fatalf("bad: %#v", actual)
  1028  	}
  1029  }
  1030  
  1031  func TestResourceRefresh_noExists(t *testing.T) {
  1032  	r := &Resource{
  1033  		Schema: map[string]*Schema{
  1034  			"foo": &Schema{
  1035  				Type:     TypeInt,
  1036  				Optional: true,
  1037  			},
  1038  		},
  1039  	}
  1040  
  1041  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
  1042  		return false, nil
  1043  	}
  1044  
  1045  	r.Read = func(d *ResourceData, m interface{}) error {
  1046  		panic("shouldn't be called")
  1047  	}
  1048  
  1049  	s := &tofu.InstanceState{
  1050  		ID: "bar",
  1051  		Attributes: map[string]string{
  1052  			"foo": "12",
  1053  		},
  1054  	}
  1055  
  1056  	actual, err := r.Refresh(s, 42)
  1057  	if err != nil {
  1058  		t.Fatalf("err: %s", err)
  1059  	}
  1060  	if actual != nil {
  1061  		t.Fatalf("should have no state")
  1062  	}
  1063  }
  1064  
  1065  func TestResourceRefresh_needsMigration(t *testing.T) {
  1066  	// Schema v2 it deals only in newfoo, which tracks foo as an int
  1067  	r := &Resource{
  1068  		SchemaVersion: 2,
  1069  		Schema: map[string]*Schema{
  1070  			"newfoo": &Schema{
  1071  				Type:     TypeInt,
  1072  				Optional: true,
  1073  			},
  1074  		},
  1075  	}
  1076  
  1077  	r.Read = func(d *ResourceData, m interface{}) error {
  1078  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1079  	}
  1080  
  1081  	r.MigrateState = func(
  1082  		v int,
  1083  		s *tofu.InstanceState,
  1084  		meta interface{}) (*tofu.InstanceState, error) {
  1085  		// Real state migration functions will probably switch on this value,
  1086  		// but we'll just assert on it for now.
  1087  		if v != 1 {
  1088  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
  1089  		}
  1090  
  1091  		if meta != 42 {
  1092  			t.Fatal("Expected meta to be passed through to the migration function")
  1093  		}
  1094  
  1095  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
  1096  		if err != nil {
  1097  			t.Fatalf("err: %#v", err)
  1098  		}
  1099  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
  1100  		delete(s.Attributes, "oldfoo")
  1101  
  1102  		return s, nil
  1103  	}
  1104  
  1105  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
  1106  	// the scale of newfoo
  1107  	s := &tofu.InstanceState{
  1108  		ID: "bar",
  1109  		Attributes: map[string]string{
  1110  			"oldfoo": "1.2",
  1111  		},
  1112  		Meta: map[string]interface{}{
  1113  			"schema_version": "1",
  1114  		},
  1115  	}
  1116  
  1117  	actual, err := r.Refresh(s, 42)
  1118  	if err != nil {
  1119  		t.Fatalf("err: %s", err)
  1120  	}
  1121  
  1122  	expected := &tofu.InstanceState{
  1123  		ID: "bar",
  1124  		Attributes: map[string]string{
  1125  			"id":     "bar",
  1126  			"newfoo": "13",
  1127  		},
  1128  		Meta: map[string]interface{}{
  1129  			"schema_version": "2",
  1130  		},
  1131  	}
  1132  
  1133  	if !reflect.DeepEqual(actual, expected) {
  1134  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1135  	}
  1136  }
  1137  
  1138  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
  1139  	r := &Resource{
  1140  		SchemaVersion: 2,
  1141  		Schema: map[string]*Schema{
  1142  			"newfoo": &Schema{
  1143  				Type:     TypeInt,
  1144  				Optional: true,
  1145  			},
  1146  		},
  1147  	}
  1148  
  1149  	r.Read = func(d *ResourceData, m interface{}) error {
  1150  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1151  	}
  1152  
  1153  	r.MigrateState = func(
  1154  		v int,
  1155  		s *tofu.InstanceState,
  1156  		meta interface{}) (*tofu.InstanceState, error) {
  1157  		t.Fatal("Migrate function shouldn't be called!")
  1158  		return nil, nil
  1159  	}
  1160  
  1161  	s := &tofu.InstanceState{
  1162  		ID: "bar",
  1163  		Attributes: map[string]string{
  1164  			"newfoo": "12",
  1165  		},
  1166  		Meta: map[string]interface{}{
  1167  			"schema_version": "2",
  1168  		},
  1169  	}
  1170  
  1171  	actual, err := r.Refresh(s, nil)
  1172  	if err != nil {
  1173  		t.Fatalf("err: %s", err)
  1174  	}
  1175  
  1176  	expected := &tofu.InstanceState{
  1177  		ID: "bar",
  1178  		Attributes: map[string]string{
  1179  			"id":     "bar",
  1180  			"newfoo": "13",
  1181  		},
  1182  		Meta: map[string]interface{}{
  1183  			"schema_version": "2",
  1184  		},
  1185  	}
  1186  
  1187  	if !reflect.DeepEqual(actual, expected) {
  1188  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1189  	}
  1190  }
  1191  
  1192  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
  1193  	r := &Resource{
  1194  		// Version 1 > Version 0
  1195  		SchemaVersion: 1,
  1196  		Schema: map[string]*Schema{
  1197  			"newfoo": &Schema{
  1198  				Type:     TypeInt,
  1199  				Optional: true,
  1200  			},
  1201  		},
  1202  	}
  1203  
  1204  	r.Read = func(d *ResourceData, m interface{}) error {
  1205  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1206  	}
  1207  
  1208  	r.MigrateState = func(
  1209  		v int,
  1210  		s *tofu.InstanceState,
  1211  		meta interface{}) (*tofu.InstanceState, error) {
  1212  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
  1213  		return s, nil
  1214  	}
  1215  
  1216  	s := &tofu.InstanceState{
  1217  		ID: "bar",
  1218  		Attributes: map[string]string{
  1219  			"oldfoo": "12",
  1220  		},
  1221  	}
  1222  
  1223  	actual, err := r.Refresh(s, nil)
  1224  	if err != nil {
  1225  		t.Fatalf("err: %s", err)
  1226  	}
  1227  
  1228  	expected := &tofu.InstanceState{
  1229  		ID: "bar",
  1230  		Attributes: map[string]string{
  1231  			"id":     "bar",
  1232  			"newfoo": "13",
  1233  		},
  1234  		Meta: map[string]interface{}{
  1235  			"schema_version": "1",
  1236  		},
  1237  	}
  1238  
  1239  	if !reflect.DeepEqual(actual, expected) {
  1240  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1241  	}
  1242  }
  1243  
  1244  func TestResourceRefresh_migrateStateErr(t *testing.T) {
  1245  	r := &Resource{
  1246  		SchemaVersion: 2,
  1247  		Schema: map[string]*Schema{
  1248  			"newfoo": &Schema{
  1249  				Type:     TypeInt,
  1250  				Optional: true,
  1251  			},
  1252  		},
  1253  	}
  1254  
  1255  	r.Read = func(d *ResourceData, m interface{}) error {
  1256  		t.Fatal("Read should never be called!")
  1257  		return nil
  1258  	}
  1259  
  1260  	r.MigrateState = func(
  1261  		v int,
  1262  		s *tofu.InstanceState,
  1263  		meta interface{}) (*tofu.InstanceState, error) {
  1264  		return s, fmt.Errorf("triggering an error")
  1265  	}
  1266  
  1267  	s := &tofu.InstanceState{
  1268  		ID: "bar",
  1269  		Attributes: map[string]string{
  1270  			"oldfoo": "12",
  1271  		},
  1272  	}
  1273  
  1274  	_, err := r.Refresh(s, nil)
  1275  	if err == nil {
  1276  		t.Fatal("expected error, but got none!")
  1277  	}
  1278  }
  1279  
  1280  func TestResourceData(t *testing.T) {
  1281  	r := &Resource{
  1282  		SchemaVersion: 2,
  1283  		Schema: map[string]*Schema{
  1284  			"foo": &Schema{
  1285  				Type:     TypeInt,
  1286  				Optional: true,
  1287  			},
  1288  		},
  1289  	}
  1290  
  1291  	state := &tofu.InstanceState{
  1292  		ID: "foo",
  1293  		Attributes: map[string]string{
  1294  			"id":  "foo",
  1295  			"foo": "42",
  1296  		},
  1297  	}
  1298  
  1299  	data := r.Data(state)
  1300  	if data.Id() != "foo" {
  1301  		t.Fatalf("err: %s", data.Id())
  1302  	}
  1303  	if v := data.Get("foo"); v != 42 {
  1304  		t.Fatalf("bad: %#v", v)
  1305  	}
  1306  
  1307  	// Set expectations
  1308  	state.Meta = map[string]interface{}{
  1309  		"schema_version": "2",
  1310  	}
  1311  
  1312  	result := data.State()
  1313  	if !reflect.DeepEqual(result, state) {
  1314  		t.Fatalf("bad: %#v", result)
  1315  	}
  1316  }
  1317  
  1318  func TestResourceData_blank(t *testing.T) {
  1319  	r := &Resource{
  1320  		SchemaVersion: 2,
  1321  		Schema: map[string]*Schema{
  1322  			"foo": &Schema{
  1323  				Type:     TypeInt,
  1324  				Optional: true,
  1325  			},
  1326  		},
  1327  	}
  1328  
  1329  	data := r.Data(nil)
  1330  	if data.Id() != "" {
  1331  		t.Fatalf("err: %s", data.Id())
  1332  	}
  1333  	if v := data.Get("foo"); v != 0 {
  1334  		t.Fatalf("bad: %#v", v)
  1335  	}
  1336  }
  1337  
  1338  func TestResourceData_timeouts(t *testing.T) {
  1339  	one := 1 * time.Second
  1340  	two := 2 * time.Second
  1341  	three := 3 * time.Second
  1342  	four := 4 * time.Second
  1343  	five := 5 * time.Second
  1344  
  1345  	timeouts := &ResourceTimeout{
  1346  		Create:  &one,
  1347  		Read:    &two,
  1348  		Update:  &three,
  1349  		Delete:  &four,
  1350  		Default: &five,
  1351  	}
  1352  
  1353  	r := &Resource{
  1354  		SchemaVersion: 2,
  1355  		Schema: map[string]*Schema{
  1356  			"foo": &Schema{
  1357  				Type:     TypeInt,
  1358  				Optional: true,
  1359  			},
  1360  		},
  1361  		Timeouts: timeouts,
  1362  	}
  1363  
  1364  	data := r.Data(nil)
  1365  	if data.Id() != "" {
  1366  		t.Fatalf("err: %s", data.Id())
  1367  	}
  1368  
  1369  	if !reflect.DeepEqual(timeouts, data.timeouts) {
  1370  		t.Fatalf("incorrect ResourceData timeouts: %#v\n", *data.timeouts)
  1371  	}
  1372  }
  1373  
  1374  func TestResource_UpgradeState(t *testing.T) {
  1375  	// While this really only calls itself and therefore doesn't test any of
  1376  	// the Resource code directly, it still serves as an example of registering
  1377  	// a StateUpgrader.
  1378  	r := &Resource{
  1379  		SchemaVersion: 2,
  1380  		Schema: map[string]*Schema{
  1381  			"newfoo": &Schema{
  1382  				Type:     TypeInt,
  1383  				Optional: true,
  1384  			},
  1385  		},
  1386  	}
  1387  
  1388  	r.StateUpgraders = []StateUpgrader{
  1389  		{
  1390  			Version: 1,
  1391  			Type: cty.Object(map[string]cty.Type{
  1392  				"id":     cty.String,
  1393  				"oldfoo": cty.Number,
  1394  			}),
  1395  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1396  
  1397  				oldfoo, ok := m["oldfoo"].(float64)
  1398  				if !ok {
  1399  					t.Fatalf("expected 1.2, got %#v", m["oldfoo"])
  1400  				}
  1401  				m["newfoo"] = int(oldfoo * 10)
  1402  				delete(m, "oldfoo")
  1403  
  1404  				return m, nil
  1405  			},
  1406  		},
  1407  	}
  1408  
  1409  	oldStateAttrs := map[string]string{
  1410  		"id":     "bar",
  1411  		"oldfoo": "1.2",
  1412  	}
  1413  
  1414  	// convert the legacy flatmap state to the json equivalent
  1415  	ty := r.StateUpgraders[0].Type
  1416  	val, err := hcl2shim.HCL2ValueFromFlatmap(oldStateAttrs, ty)
  1417  	if err != nil {
  1418  		t.Fatal(err)
  1419  	}
  1420  	js, err := ctyjson.Marshal(val, ty)
  1421  	if err != nil {
  1422  		t.Fatal(err)
  1423  	}
  1424  
  1425  	// unmarshal the state using the json default types
  1426  	var m map[string]interface{}
  1427  	if err := json.Unmarshal(js, &m); err != nil {
  1428  		t.Fatal(err)
  1429  	}
  1430  
  1431  	actual, err := r.StateUpgraders[0].Upgrade(m, nil)
  1432  	if err != nil {
  1433  		t.Fatalf("err: %s", err)
  1434  	}
  1435  
  1436  	expected := map[string]interface{}{
  1437  		"id":     "bar",
  1438  		"newfoo": 12,
  1439  	}
  1440  
  1441  	if !reflect.DeepEqual(expected, actual) {
  1442  		t.Fatalf("expected: %#v\ngot: %#v\n", expected, actual)
  1443  	}
  1444  }
  1445  
  1446  func TestResource_ValidateUpgradeState(t *testing.T) {
  1447  	r := &Resource{
  1448  		SchemaVersion: 3,
  1449  		Schema: map[string]*Schema{
  1450  			"newfoo": &Schema{
  1451  				Type:     TypeInt,
  1452  				Optional: true,
  1453  			},
  1454  		},
  1455  	}
  1456  
  1457  	if err := r.InternalValidate(nil, true); err != nil {
  1458  		t.Fatal(err)
  1459  	}
  1460  
  1461  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1462  		Version: 2,
  1463  		Type: cty.Object(map[string]cty.Type{
  1464  			"id": cty.String,
  1465  		}),
  1466  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1467  			return m, nil
  1468  		},
  1469  	})
  1470  	if err := r.InternalValidate(nil, true); err != nil {
  1471  		t.Fatal(err)
  1472  	}
  1473  
  1474  	// check for missing type
  1475  	r.StateUpgraders[0].Type = cty.Type{}
  1476  	if err := r.InternalValidate(nil, true); err == nil {
  1477  		t.Fatal("StateUpgrader must have type")
  1478  	}
  1479  	r.StateUpgraders[0].Type = cty.Object(map[string]cty.Type{
  1480  		"id": cty.String,
  1481  	})
  1482  
  1483  	// check for missing Upgrade func
  1484  	r.StateUpgraders[0].Upgrade = nil
  1485  	if err := r.InternalValidate(nil, true); err == nil {
  1486  		t.Fatal("StateUpgrader must have an Upgrade func")
  1487  	}
  1488  	r.StateUpgraders[0].Upgrade = func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1489  		return m, nil
  1490  	}
  1491  
  1492  	// check for skipped version
  1493  	r.StateUpgraders[0].Version = 0
  1494  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1495  		Version: 2,
  1496  		Type: cty.Object(map[string]cty.Type{
  1497  			"id": cty.String,
  1498  		}),
  1499  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1500  			return m, nil
  1501  		},
  1502  	})
  1503  	if err := r.InternalValidate(nil, true); err == nil {
  1504  		t.Fatal("StateUpgraders cannot skip versions")
  1505  	}
  1506  
  1507  	// add the missing version, but fail because it's still out of order
  1508  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1509  		Version: 1,
  1510  		Type: cty.Object(map[string]cty.Type{
  1511  			"id": cty.String,
  1512  		}),
  1513  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1514  			return m, nil
  1515  		},
  1516  	})
  1517  	if err := r.InternalValidate(nil, true); err == nil {
  1518  		t.Fatal("upgraders must be defined in order")
  1519  	}
  1520  
  1521  	r.StateUpgraders[1], r.StateUpgraders[2] = r.StateUpgraders[2], r.StateUpgraders[1]
  1522  	if err := r.InternalValidate(nil, true); err != nil {
  1523  		t.Fatal(err)
  1524  	}
  1525  
  1526  	// can't add an upgrader for a schema >= the current version
  1527  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1528  		Version: 3,
  1529  		Type: cty.Object(map[string]cty.Type{
  1530  			"id": cty.String,
  1531  		}),
  1532  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1533  			return m, nil
  1534  		},
  1535  	})
  1536  	if err := r.InternalValidate(nil, true); err == nil {
  1537  		t.Fatal("StateUpgraders cannot have a version >= current SchemaVersion")
  1538  	}
  1539  }
  1540  
  1541  // The legacy provider will need to be able to handle both types of schema
  1542  // transformations, which has been retrofitted into the Refresh method.
  1543  func TestResource_migrateAndUpgrade(t *testing.T) {
  1544  	r := &Resource{
  1545  		SchemaVersion: 4,
  1546  		Schema: map[string]*Schema{
  1547  			"four": {
  1548  				Type:     TypeInt,
  1549  				Required: true,
  1550  			},
  1551  		},
  1552  		// this MigrateState will take the state to version 2
  1553  		MigrateState: func(v int, is *tofu.InstanceState, _ interface{}) (*tofu.InstanceState, error) {
  1554  			switch v {
  1555  			case 0:
  1556  				_, ok := is.Attributes["zero"]
  1557  				if !ok {
  1558  					return nil, fmt.Errorf("zero not found in %#v", is.Attributes)
  1559  				}
  1560  				is.Attributes["one"] = "1"
  1561  				delete(is.Attributes, "zero")
  1562  				fallthrough
  1563  			case 1:
  1564  				_, ok := is.Attributes["one"]
  1565  				if !ok {
  1566  					return nil, fmt.Errorf("one not found in %#v", is.Attributes)
  1567  				}
  1568  				is.Attributes["two"] = "2"
  1569  				delete(is.Attributes, "one")
  1570  			default:
  1571  				return nil, fmt.Errorf("invalid schema version %d", v)
  1572  			}
  1573  			return is, nil
  1574  		},
  1575  	}
  1576  
  1577  	r.Read = func(d *ResourceData, m interface{}) error {
  1578  		return d.Set("four", 4)
  1579  	}
  1580  
  1581  	r.StateUpgraders = []StateUpgrader{
  1582  		{
  1583  			Version: 2,
  1584  			Type: cty.Object(map[string]cty.Type{
  1585  				"id":  cty.String,
  1586  				"two": cty.Number,
  1587  			}),
  1588  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1589  				_, ok := m["two"].(float64)
  1590  				if !ok {
  1591  					return nil, fmt.Errorf("two not found in %#v", m)
  1592  				}
  1593  				m["three"] = float64(3)
  1594  				delete(m, "two")
  1595  				return m, nil
  1596  			},
  1597  		},
  1598  		{
  1599  			Version: 3,
  1600  			Type: cty.Object(map[string]cty.Type{
  1601  				"id":    cty.String,
  1602  				"three": cty.Number,
  1603  			}),
  1604  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1605  				_, ok := m["three"].(float64)
  1606  				if !ok {
  1607  					return nil, fmt.Errorf("three not found in %#v", m)
  1608  				}
  1609  				m["four"] = float64(4)
  1610  				delete(m, "three")
  1611  				return m, nil
  1612  			},
  1613  		},
  1614  	}
  1615  
  1616  	testStates := []*tofu.InstanceState{
  1617  		{
  1618  			ID: "bar",
  1619  			Attributes: map[string]string{
  1620  				"id":   "bar",
  1621  				"zero": "0",
  1622  			},
  1623  			Meta: map[string]interface{}{
  1624  				"schema_version": "0",
  1625  			},
  1626  		},
  1627  		{
  1628  			ID: "bar",
  1629  			Attributes: map[string]string{
  1630  				"id":  "bar",
  1631  				"one": "1",
  1632  			},
  1633  			Meta: map[string]interface{}{
  1634  				"schema_version": "1",
  1635  			},
  1636  		},
  1637  		{
  1638  			ID: "bar",
  1639  			Attributes: map[string]string{
  1640  				"id":  "bar",
  1641  				"two": "2",
  1642  			},
  1643  			Meta: map[string]interface{}{
  1644  				"schema_version": "2",
  1645  			},
  1646  		},
  1647  		{
  1648  			ID: "bar",
  1649  			Attributes: map[string]string{
  1650  				"id":    "bar",
  1651  				"three": "3",
  1652  			},
  1653  			Meta: map[string]interface{}{
  1654  				"schema_version": "3",
  1655  			},
  1656  		},
  1657  		{
  1658  			ID: "bar",
  1659  			Attributes: map[string]string{
  1660  				"id":   "bar",
  1661  				"four": "4",
  1662  			},
  1663  			Meta: map[string]interface{}{
  1664  				"schema_version": "4",
  1665  			},
  1666  		},
  1667  	}
  1668  
  1669  	for i, s := range testStates {
  1670  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1671  			newState, err := r.Refresh(s, nil)
  1672  			if err != nil {
  1673  				t.Fatal(err)
  1674  			}
  1675  
  1676  			expected := &tofu.InstanceState{
  1677  				ID: "bar",
  1678  				Attributes: map[string]string{
  1679  					"id":   "bar",
  1680  					"four": "4",
  1681  				},
  1682  				Meta: map[string]interface{}{
  1683  					"schema_version": "4",
  1684  				},
  1685  			}
  1686  
  1687  			if !cmp.Equal(expected, newState, equateEmpty) {
  1688  				t.Fatal(cmp.Diff(expected, newState, equateEmpty))
  1689  			}
  1690  		})
  1691  	}
  1692  }