github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/plugin/grpc_provider_test.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  	proto "github.com/hashicorp/terraform/internal/tfplugin5"
    15  	"github.com/hashicorp/terraform/plugin/convert"
    16  	"github.com/hashicorp/terraform/terraform"
    17  	"github.com/zclconf/go-cty/cty"
    18  	"github.com/zclconf/go-cty/cty/msgpack"
    19  )
    20  
    21  // The GRPCProviderServer will directly implement the go protobuf server
    22  var _ proto.ProviderServer = (*GRPCProviderServer)(nil)
    23  
    24  var (
    25  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    26  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    27  	equateEmpty   = cmpopts.EquateEmpty()
    28  )
    29  
    30  func TestUpgradeState_jsonState(t *testing.T) {
    31  	r := &schema.Resource{
    32  		SchemaVersion: 2,
    33  		Schema: map[string]*schema.Schema{
    34  			"two": {
    35  				Type:     schema.TypeInt,
    36  				Optional: true,
    37  			},
    38  		},
    39  	}
    40  
    41  	r.StateUpgraders = []schema.StateUpgrader{
    42  		{
    43  			Version: 0,
    44  			Type: cty.Object(map[string]cty.Type{
    45  				"id":   cty.String,
    46  				"zero": cty.Number,
    47  			}),
    48  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
    49  				_, ok := m["zero"].(float64)
    50  				if !ok {
    51  					return nil, fmt.Errorf("zero not found in %#v", m)
    52  				}
    53  				m["one"] = float64(1)
    54  				delete(m, "zero")
    55  				return m, nil
    56  			},
    57  		},
    58  		{
    59  			Version: 1,
    60  			Type: cty.Object(map[string]cty.Type{
    61  				"id":  cty.String,
    62  				"one": cty.Number,
    63  			}),
    64  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
    65  				_, ok := m["one"].(float64)
    66  				if !ok {
    67  					return nil, fmt.Errorf("one not found in %#v", m)
    68  				}
    69  				m["two"] = float64(2)
    70  				delete(m, "one")
    71  				return m, nil
    72  			},
    73  		},
    74  	}
    75  
    76  	server := &GRPCProviderServer{
    77  		provider: &schema.Provider{
    78  			ResourcesMap: map[string]*schema.Resource{
    79  				"test": r,
    80  			},
    81  		},
    82  	}
    83  
    84  	req := &proto.UpgradeResourceState_Request{
    85  		TypeName: "test",
    86  		Version:  0,
    87  		RawState: &proto.RawState{
    88  			Json: []byte(`{"id":"bar","zero":0}`),
    89  		},
    90  	}
    91  
    92  	resp, err := server.UpgradeResourceState(nil, req)
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	if len(resp.Diagnostics) > 0 {
    98  		for _, d := range resp.Diagnostics {
    99  			t.Errorf("%#v", d)
   100  		}
   101  		t.Fatal("error")
   102  	}
   103  
   104  	val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType())
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	expected := cty.ObjectVal(map[string]cty.Value{
   110  		"id":  cty.StringVal("bar"),
   111  		"two": cty.NumberIntVal(2),
   112  	})
   113  
   114  	if !cmp.Equal(expected, val, valueComparer, equateEmpty) {
   115  		t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty))
   116  	}
   117  }
   118  
   119  func TestUpgradeState_removedAttr(t *testing.T) {
   120  	r1 := &schema.Resource{
   121  		Schema: map[string]*schema.Schema{
   122  			"two": {
   123  				Type:     schema.TypeString,
   124  				Optional: true,
   125  			},
   126  		},
   127  	}
   128  
   129  	r2 := &schema.Resource{
   130  		Schema: map[string]*schema.Schema{
   131  			"multi": {
   132  				Type:     schema.TypeSet,
   133  				Optional: true,
   134  				Elem: &schema.Resource{
   135  					Schema: map[string]*schema.Schema{
   136  						"set": {
   137  							Type:     schema.TypeSet,
   138  							Optional: true,
   139  							Elem: &schema.Resource{
   140  								Schema: map[string]*schema.Schema{
   141  									"required": {
   142  										Type:     schema.TypeString,
   143  										Required: true,
   144  									},
   145  								},
   146  							},
   147  						},
   148  					},
   149  				},
   150  			},
   151  		},
   152  	}
   153  
   154  	r3 := &schema.Resource{
   155  		Schema: map[string]*schema.Schema{
   156  			"config_mode_attr": {
   157  				Type:       schema.TypeList,
   158  				ConfigMode: schema.SchemaConfigModeAttr,
   159  				Optional:   true,
   160  				Elem: &schema.Resource{
   161  					Schema: map[string]*schema.Schema{
   162  						"foo": {
   163  							Type:     schema.TypeString,
   164  							Optional: true,
   165  						},
   166  					},
   167  				},
   168  			},
   169  		},
   170  	}
   171  
   172  	p := &schema.Provider{
   173  		ResourcesMap: map[string]*schema.Resource{
   174  			"r1": r1,
   175  			"r2": r2,
   176  			"r3": r3,
   177  		},
   178  	}
   179  
   180  	server := &GRPCProviderServer{
   181  		provider: p,
   182  	}
   183  
   184  	for _, tc := range []struct {
   185  		name     string
   186  		raw      string
   187  		expected cty.Value
   188  	}{
   189  		{
   190  			name: "r1",
   191  			raw:  `{"id":"bar","removed":"removed","two":"2"}`,
   192  			expected: cty.ObjectVal(map[string]cty.Value{
   193  				"id":  cty.StringVal("bar"),
   194  				"two": cty.StringVal("2"),
   195  			}),
   196  		},
   197  		{
   198  			name: "r2",
   199  			raw:  `{"id":"bar","multi":[{"set":[{"required":"ok","removed":"removed"}]}]}`,
   200  			expected: cty.ObjectVal(map[string]cty.Value{
   201  				"id": cty.StringVal("bar"),
   202  				"multi": cty.SetVal([]cty.Value{
   203  					cty.ObjectVal(map[string]cty.Value{
   204  						"set": cty.SetVal([]cty.Value{
   205  							cty.ObjectVal(map[string]cty.Value{
   206  								"required": cty.StringVal("ok"),
   207  							}),
   208  						}),
   209  					}),
   210  				}),
   211  			}),
   212  		},
   213  		{
   214  			name: "r3",
   215  			raw:  `{"id":"bar","config_mode_attr":[{"foo":"ok","removed":"removed"}]}`,
   216  			expected: cty.ObjectVal(map[string]cty.Value{
   217  				"id": cty.StringVal("bar"),
   218  				"config_mode_attr": cty.ListVal([]cty.Value{
   219  					cty.ObjectVal(map[string]cty.Value{
   220  						"foo": cty.StringVal("ok"),
   221  					}),
   222  				}),
   223  			}),
   224  		},
   225  	} {
   226  		t.Run(tc.name, func(t *testing.T) {
   227  			req := &proto.UpgradeResourceState_Request{
   228  				TypeName: tc.name,
   229  				Version:  0,
   230  				RawState: &proto.RawState{
   231  					Json: []byte(tc.raw),
   232  				},
   233  			}
   234  			resp, err := server.UpgradeResourceState(nil, req)
   235  			if err != nil {
   236  				t.Fatal(err)
   237  			}
   238  
   239  			if len(resp.Diagnostics) > 0 {
   240  				for _, d := range resp.Diagnostics {
   241  					t.Errorf("%#v", d)
   242  				}
   243  				t.Fatal("error")
   244  			}
   245  			val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, p.ResourcesMap[tc.name].CoreConfigSchema().ImpliedType())
   246  			if err != nil {
   247  				t.Fatal(err)
   248  			}
   249  			if !tc.expected.RawEquals(val) {
   250  				t.Fatalf("\nexpected: %#v\ngot:      %#v\n", tc.expected, val)
   251  			}
   252  		})
   253  	}
   254  
   255  }
   256  
   257  func TestUpgradeState_flatmapState(t *testing.T) {
   258  	r := &schema.Resource{
   259  		SchemaVersion: 4,
   260  		Schema: map[string]*schema.Schema{
   261  			"four": {
   262  				Type:     schema.TypeInt,
   263  				Required: true,
   264  			},
   265  			"block": {
   266  				Type:     schema.TypeList,
   267  				Optional: true,
   268  				Elem: &schema.Resource{
   269  					Schema: map[string]*schema.Schema{
   270  						"attr": {
   271  							Type:     schema.TypeString,
   272  							Optional: true,
   273  						},
   274  					},
   275  				},
   276  			},
   277  		},
   278  		// this MigrateState will take the state to version 2
   279  		MigrateState: func(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) {
   280  			switch v {
   281  			case 0:
   282  				_, ok := is.Attributes["zero"]
   283  				if !ok {
   284  					return nil, fmt.Errorf("zero not found in %#v", is.Attributes)
   285  				}
   286  				is.Attributes["one"] = "1"
   287  				delete(is.Attributes, "zero")
   288  				fallthrough
   289  			case 1:
   290  				_, ok := is.Attributes["one"]
   291  				if !ok {
   292  					return nil, fmt.Errorf("one not found in %#v", is.Attributes)
   293  				}
   294  				is.Attributes["two"] = "2"
   295  				delete(is.Attributes, "one")
   296  			default:
   297  				return nil, fmt.Errorf("invalid schema version %d", v)
   298  			}
   299  			return is, nil
   300  		},
   301  	}
   302  
   303  	r.StateUpgraders = []schema.StateUpgrader{
   304  		{
   305  			Version: 2,
   306  			Type: cty.Object(map[string]cty.Type{
   307  				"id":  cty.String,
   308  				"two": cty.Number,
   309  			}),
   310  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
   311  				_, ok := m["two"].(float64)
   312  				if !ok {
   313  					return nil, fmt.Errorf("two not found in %#v", m)
   314  				}
   315  				m["three"] = float64(3)
   316  				delete(m, "two")
   317  				return m, nil
   318  			},
   319  		},
   320  		{
   321  			Version: 3,
   322  			Type: cty.Object(map[string]cty.Type{
   323  				"id":    cty.String,
   324  				"three": cty.Number,
   325  			}),
   326  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
   327  				_, ok := m["three"].(float64)
   328  				if !ok {
   329  					return nil, fmt.Errorf("three not found in %#v", m)
   330  				}
   331  				m["four"] = float64(4)
   332  				delete(m, "three")
   333  				return m, nil
   334  			},
   335  		},
   336  	}
   337  
   338  	server := &GRPCProviderServer{
   339  		provider: &schema.Provider{
   340  			ResourcesMap: map[string]*schema.Resource{
   341  				"test": r,
   342  			},
   343  		},
   344  	}
   345  
   346  	testReqs := []*proto.UpgradeResourceState_Request{
   347  		{
   348  			TypeName: "test",
   349  			Version:  0,
   350  			RawState: &proto.RawState{
   351  				Flatmap: map[string]string{
   352  					"id":   "bar",
   353  					"zero": "0",
   354  				},
   355  			},
   356  		},
   357  		{
   358  			TypeName: "test",
   359  			Version:  1,
   360  			RawState: &proto.RawState{
   361  				Flatmap: map[string]string{
   362  					"id":  "bar",
   363  					"one": "1",
   364  				},
   365  			},
   366  		},
   367  		// two and  up could be stored in flatmap or json states
   368  		{
   369  			TypeName: "test",
   370  			Version:  2,
   371  			RawState: &proto.RawState{
   372  				Flatmap: map[string]string{
   373  					"id":  "bar",
   374  					"two": "2",
   375  				},
   376  			},
   377  		},
   378  		{
   379  			TypeName: "test",
   380  			Version:  2,
   381  			RawState: &proto.RawState{
   382  				Json: []byte(`{"id":"bar","two":2}`),
   383  			},
   384  		},
   385  		{
   386  			TypeName: "test",
   387  			Version:  3,
   388  			RawState: &proto.RawState{
   389  				Flatmap: map[string]string{
   390  					"id":    "bar",
   391  					"three": "3",
   392  				},
   393  			},
   394  		},
   395  		{
   396  			TypeName: "test",
   397  			Version:  3,
   398  			RawState: &proto.RawState{
   399  				Json: []byte(`{"id":"bar","three":3}`),
   400  			},
   401  		},
   402  		{
   403  			TypeName: "test",
   404  			Version:  4,
   405  			RawState: &proto.RawState{
   406  				Flatmap: map[string]string{
   407  					"id":   "bar",
   408  					"four": "4",
   409  				},
   410  			},
   411  		},
   412  		{
   413  			TypeName: "test",
   414  			Version:  4,
   415  			RawState: &proto.RawState{
   416  				Json: []byte(`{"id":"bar","four":4}`),
   417  			},
   418  		},
   419  	}
   420  
   421  	for i, req := range testReqs {
   422  		t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) {
   423  			resp, err := server.UpgradeResourceState(nil, req)
   424  			if err != nil {
   425  				t.Fatal(err)
   426  			}
   427  
   428  			if len(resp.Diagnostics) > 0 {
   429  				for _, d := range resp.Diagnostics {
   430  					t.Errorf("%#v", d)
   431  				}
   432  				t.Fatal("error")
   433  			}
   434  
   435  			val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType())
   436  			if err != nil {
   437  				t.Fatal(err)
   438  			}
   439  
   440  			expected := cty.ObjectVal(map[string]cty.Value{
   441  				"block": cty.ListValEmpty(cty.Object(map[string]cty.Type{"attr": cty.String})),
   442  				"id":    cty.StringVal("bar"),
   443  				"four":  cty.NumberIntVal(4),
   444  			})
   445  
   446  			if !cmp.Equal(expected, val, valueComparer, equateEmpty) {
   447  				t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty))
   448  			}
   449  		})
   450  	}
   451  }
   452  
   453  func TestUpgradeState_flatmapStateMissingMigrateState(t *testing.T) {
   454  	r := &schema.Resource{
   455  		SchemaVersion: 1,
   456  		Schema: map[string]*schema.Schema{
   457  			"one": {
   458  				Type:     schema.TypeInt,
   459  				Required: true,
   460  			},
   461  		},
   462  	}
   463  
   464  	server := &GRPCProviderServer{
   465  		provider: &schema.Provider{
   466  			ResourcesMap: map[string]*schema.Resource{
   467  				"test": r,
   468  			},
   469  		},
   470  	}
   471  
   472  	testReqs := []*proto.UpgradeResourceState_Request{
   473  		{
   474  			TypeName: "test",
   475  			Version:  0,
   476  			RawState: &proto.RawState{
   477  				Flatmap: map[string]string{
   478  					"id":  "bar",
   479  					"one": "1",
   480  				},
   481  			},
   482  		},
   483  		{
   484  			TypeName: "test",
   485  			Version:  1,
   486  			RawState: &proto.RawState{
   487  				Flatmap: map[string]string{
   488  					"id":  "bar",
   489  					"one": "1",
   490  				},
   491  			},
   492  		},
   493  		{
   494  			TypeName: "test",
   495  			Version:  1,
   496  			RawState: &proto.RawState{
   497  				Json: []byte(`{"id":"bar","one":1}`),
   498  			},
   499  		},
   500  	}
   501  
   502  	for i, req := range testReqs {
   503  		t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) {
   504  			resp, err := server.UpgradeResourceState(nil, req)
   505  			if err != nil {
   506  				t.Fatal(err)
   507  			}
   508  
   509  			if len(resp.Diagnostics) > 0 {
   510  				for _, d := range resp.Diagnostics {
   511  					t.Errorf("%#v", d)
   512  				}
   513  				t.Fatal("error")
   514  			}
   515  
   516  			val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType())
   517  			if err != nil {
   518  				t.Fatal(err)
   519  			}
   520  
   521  			expected := cty.ObjectVal(map[string]cty.Value{
   522  				"id":  cty.StringVal("bar"),
   523  				"one": cty.NumberIntVal(1),
   524  			})
   525  
   526  			if !cmp.Equal(expected, val, valueComparer, equateEmpty) {
   527  				t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty))
   528  			}
   529  		})
   530  	}
   531  }
   532  
   533  func TestPlanResourceChange(t *testing.T) {
   534  	r := &schema.Resource{
   535  		SchemaVersion: 4,
   536  		Schema: map[string]*schema.Schema{
   537  			"foo": {
   538  				Type:     schema.TypeInt,
   539  				Optional: true,
   540  			},
   541  		},
   542  	}
   543  
   544  	server := &GRPCProviderServer{
   545  		provider: &schema.Provider{
   546  			ResourcesMap: map[string]*schema.Resource{
   547  				"test": r,
   548  			},
   549  		},
   550  	}
   551  
   552  	schema := r.CoreConfigSchema()
   553  	priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType())
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	// A propsed state with only the ID unknown will produce a nil diff, and
   559  	// should return the propsed state value.
   560  	proposedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
   561  		"id": cty.UnknownVal(cty.String),
   562  	}))
   563  	if err != nil {
   564  		t.Fatal(err)
   565  	}
   566  	proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType())
   567  	if err != nil {
   568  		t.Fatal(err)
   569  	}
   570  
   571  	testReq := &proto.PlanResourceChange_Request{
   572  		TypeName: "test",
   573  		PriorState: &proto.DynamicValue{
   574  			Msgpack: priorState,
   575  		},
   576  		ProposedNewState: &proto.DynamicValue{
   577  			Msgpack: proposedState,
   578  		},
   579  	}
   580  
   581  	resp, err := server.PlanResourceChange(context.Background(), testReq)
   582  	if err != nil {
   583  		t.Fatal(err)
   584  	}
   585  
   586  	plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.Msgpack, schema.ImpliedType())
   587  	if err != nil {
   588  		t.Fatal(err)
   589  	}
   590  
   591  	if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) {
   592  		t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer))
   593  	}
   594  }
   595  
   596  func TestApplyResourceChange(t *testing.T) {
   597  	r := &schema.Resource{
   598  		SchemaVersion: 4,
   599  		Schema: map[string]*schema.Schema{
   600  			"foo": {
   601  				Type:     schema.TypeInt,
   602  				Optional: true,
   603  			},
   604  		},
   605  		Create: func(rd *schema.ResourceData, _ interface{}) error {
   606  			rd.SetId("bar")
   607  			return nil
   608  		},
   609  	}
   610  
   611  	server := &GRPCProviderServer{
   612  		provider: &schema.Provider{
   613  			ResourcesMap: map[string]*schema.Resource{
   614  				"test": r,
   615  			},
   616  		},
   617  	}
   618  
   619  	schema := r.CoreConfigSchema()
   620  	priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType())
   621  	if err != nil {
   622  		t.Fatal(err)
   623  	}
   624  
   625  	// A proposed state with only the ID unknown will produce a nil diff, and
   626  	// should return the proposed state value.
   627  	plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
   628  		"id": cty.UnknownVal(cty.String),
   629  	}))
   630  	if err != nil {
   631  		t.Fatal(err)
   632  	}
   633  	plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType())
   634  	if err != nil {
   635  		t.Fatal(err)
   636  	}
   637  
   638  	testReq := &proto.ApplyResourceChange_Request{
   639  		TypeName: "test",
   640  		PriorState: &proto.DynamicValue{
   641  			Msgpack: priorState,
   642  		},
   643  		PlannedState: &proto.DynamicValue{
   644  			Msgpack: plannedState,
   645  		},
   646  	}
   647  
   648  	resp, err := server.ApplyResourceChange(context.Background(), testReq)
   649  	if err != nil {
   650  		t.Fatal(err)
   651  	}
   652  
   653  	newStateVal, err := msgpack.Unmarshal(resp.NewState.Msgpack, schema.ImpliedType())
   654  	if err != nil {
   655  		t.Fatal(err)
   656  	}
   657  
   658  	id := newStateVal.GetAttr("id").AsString()
   659  	if id != "bar" {
   660  		t.Fatalf("incorrect final state: %#v\n", newStateVal)
   661  	}
   662  }
   663  
   664  func TestPrepareProviderConfig(t *testing.T) {
   665  	for _, tc := range []struct {
   666  		Name         string
   667  		Schema       map[string]*schema.Schema
   668  		ConfigVal    cty.Value
   669  		ExpectError  string
   670  		ExpectConfig cty.Value
   671  	}{
   672  		{
   673  			Name: "test prepare",
   674  			Schema: map[string]*schema.Schema{
   675  				"foo": &schema.Schema{
   676  					Type:     schema.TypeString,
   677  					Optional: true,
   678  				},
   679  			},
   680  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   681  				"foo": cty.StringVal("bar"),
   682  			}),
   683  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   684  				"foo": cty.StringVal("bar"),
   685  			}),
   686  		},
   687  		{
   688  			Name: "test default",
   689  			Schema: map[string]*schema.Schema{
   690  				"foo": &schema.Schema{
   691  					Type:     schema.TypeString,
   692  					Optional: true,
   693  					Default:  "default",
   694  				},
   695  			},
   696  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   697  				"foo": cty.NullVal(cty.String),
   698  			}),
   699  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   700  				"foo": cty.StringVal("default"),
   701  			}),
   702  		},
   703  		{
   704  			Name: "test defaultfunc",
   705  			Schema: map[string]*schema.Schema{
   706  				"foo": &schema.Schema{
   707  					Type:     schema.TypeString,
   708  					Optional: true,
   709  					DefaultFunc: func() (interface{}, error) {
   710  						return "defaultfunc", nil
   711  					},
   712  				},
   713  			},
   714  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   715  				"foo": cty.NullVal(cty.String),
   716  			}),
   717  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   718  				"foo": cty.StringVal("defaultfunc"),
   719  			}),
   720  		},
   721  		{
   722  			Name: "test default required",
   723  			Schema: map[string]*schema.Schema{
   724  				"foo": &schema.Schema{
   725  					Type:     schema.TypeString,
   726  					Required: true,
   727  					DefaultFunc: func() (interface{}, error) {
   728  						return "defaultfunc", nil
   729  					},
   730  				},
   731  			},
   732  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   733  				"foo": cty.NullVal(cty.String),
   734  			}),
   735  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   736  				"foo": cty.StringVal("defaultfunc"),
   737  			}),
   738  		},
   739  		{
   740  			Name: "test incorrect type",
   741  			Schema: map[string]*schema.Schema{
   742  				"foo": &schema.Schema{
   743  					Type:     schema.TypeString,
   744  					Required: true,
   745  				},
   746  			},
   747  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   748  				"foo": cty.NumberIntVal(3),
   749  			}),
   750  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   751  				"foo": cty.StringVal("3"),
   752  			}),
   753  		},
   754  		{
   755  			Name: "test incorrect default type",
   756  			Schema: map[string]*schema.Schema{
   757  				"foo": &schema.Schema{
   758  					Type:     schema.TypeString,
   759  					Optional: true,
   760  					Default:  true,
   761  				},
   762  			},
   763  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   764  				"foo": cty.NullVal(cty.String),
   765  			}),
   766  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   767  				"foo": cty.StringVal("true"),
   768  			}),
   769  		},
   770  		{
   771  			Name: "test incorrect default bool type",
   772  			Schema: map[string]*schema.Schema{
   773  				"foo": &schema.Schema{
   774  					Type:     schema.TypeBool,
   775  					Optional: true,
   776  					Default:  "",
   777  				},
   778  			},
   779  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   780  				"foo": cty.NullVal(cty.Bool),
   781  			}),
   782  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   783  				"foo": cty.False,
   784  			}),
   785  		},
   786  		{
   787  			Name: "test deprecated default",
   788  			Schema: map[string]*schema.Schema{
   789  				"foo": &schema.Schema{
   790  					Type:     schema.TypeString,
   791  					Optional: true,
   792  					Default:  "do not use",
   793  					Removed:  "don't use this",
   794  				},
   795  			},
   796  			ConfigVal: cty.ObjectVal(map[string]cty.Value{
   797  				"foo": cty.NullVal(cty.String),
   798  			}),
   799  			ExpectConfig: cty.ObjectVal(map[string]cty.Value{
   800  				"foo": cty.NullVal(cty.String),
   801  			}),
   802  		},
   803  	} {
   804  		t.Run(tc.Name, func(t *testing.T) {
   805  			server := &GRPCProviderServer{
   806  				provider: &schema.Provider{
   807  					Schema: tc.Schema,
   808  				},
   809  			}
   810  
   811  			block := schema.InternalMap(tc.Schema).CoreConfigSchema()
   812  
   813  			rawConfig, err := msgpack.Marshal(tc.ConfigVal, block.ImpliedType())
   814  			if err != nil {
   815  				t.Fatal(err)
   816  			}
   817  
   818  			testReq := &proto.PrepareProviderConfig_Request{
   819  				Config: &proto.DynamicValue{
   820  					Msgpack: rawConfig,
   821  				},
   822  			}
   823  
   824  			resp, err := server.PrepareProviderConfig(nil, testReq)
   825  			if err != nil {
   826  				t.Fatal(err)
   827  			}
   828  
   829  			if tc.ExpectError != "" && len(resp.Diagnostics) > 0 {
   830  				for _, d := range resp.Diagnostics {
   831  					if !strings.Contains(d.Summary, tc.ExpectError) {
   832  						t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail)
   833  					}
   834  				}
   835  				return
   836  			}
   837  
   838  			// we should have no errors past this point
   839  			for _, d := range resp.Diagnostics {
   840  				if d.Severity == proto.Diagnostic_ERROR {
   841  					t.Fatal(resp.Diagnostics)
   842  				}
   843  			}
   844  
   845  			val, err := msgpack.Unmarshal(resp.PreparedConfig.Msgpack, block.ImpliedType())
   846  			if err != nil {
   847  				t.Fatal(err)
   848  			}
   849  
   850  			if tc.ExpectConfig.GoString() != val.GoString() {
   851  				t.Fatalf("\nexpected: %#v\ngot: %#v", tc.ExpectConfig, val)
   852  			}
   853  		})
   854  	}
   855  }
   856  
   857  func TestGetSchemaTimeouts(t *testing.T) {
   858  	r := &schema.Resource{
   859  		SchemaVersion: 4,
   860  		Timeouts: &schema.ResourceTimeout{
   861  			Create:  schema.DefaultTimeout(time.Second),
   862  			Read:    schema.DefaultTimeout(2 * time.Second),
   863  			Update:  schema.DefaultTimeout(3 * time.Second),
   864  			Default: schema.DefaultTimeout(10 * time.Second),
   865  		},
   866  		Schema: map[string]*schema.Schema{
   867  			"foo": {
   868  				Type:     schema.TypeInt,
   869  				Optional: true,
   870  			},
   871  		},
   872  	}
   873  
   874  	// verify that the timeouts appear in the schema as defined
   875  	block := r.CoreConfigSchema()
   876  	timeoutsBlock := block.BlockTypes["timeouts"]
   877  	if timeoutsBlock == nil {
   878  		t.Fatal("missing timeouts in schema")
   879  	}
   880  
   881  	if timeoutsBlock.Attributes["create"] == nil {
   882  		t.Fatal("missing create timeout in schema")
   883  	}
   884  	if timeoutsBlock.Attributes["read"] == nil {
   885  		t.Fatal("missing read timeout in schema")
   886  	}
   887  	if timeoutsBlock.Attributes["update"] == nil {
   888  		t.Fatal("missing update timeout in schema")
   889  	}
   890  	if d := timeoutsBlock.Attributes["delete"]; d != nil {
   891  		t.Fatalf("unexpected delete timeout in schema: %#v", d)
   892  	}
   893  	if timeoutsBlock.Attributes["default"] == nil {
   894  		t.Fatal("missing default timeout in schema")
   895  	}
   896  }
   897  
   898  func TestNormalizeNullValues(t *testing.T) {
   899  	for i, tc := range []struct {
   900  		Src, Dst, Expect cty.Value
   901  		Apply            bool
   902  	}{
   903  		{
   904  			// The known set value is copied over the null set value
   905  			Src: cty.ObjectVal(map[string]cty.Value{
   906  				"set": cty.SetVal([]cty.Value{
   907  					cty.ObjectVal(map[string]cty.Value{
   908  						"foo": cty.NullVal(cty.String),
   909  					}),
   910  				}),
   911  			}),
   912  			Dst: cty.ObjectVal(map[string]cty.Value{
   913  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   914  					"foo": cty.String,
   915  				}))),
   916  			}),
   917  			Expect: cty.ObjectVal(map[string]cty.Value{
   918  				"set": cty.SetVal([]cty.Value{
   919  					cty.ObjectVal(map[string]cty.Value{
   920  						"foo": cty.NullVal(cty.String),
   921  					}),
   922  				}),
   923  			}),
   924  			Apply: true,
   925  		},
   926  		{
   927  			// A zero set value is kept
   928  			Src: cty.ObjectVal(map[string]cty.Value{
   929  				"set": cty.SetValEmpty(cty.String),
   930  			}),
   931  			Dst: cty.ObjectVal(map[string]cty.Value{
   932  				"set": cty.SetValEmpty(cty.String),
   933  			}),
   934  			Expect: cty.ObjectVal(map[string]cty.Value{
   935  				"set": cty.SetValEmpty(cty.String),
   936  			}),
   937  		},
   938  		{
   939  			// The known set value is copied over the null set value
   940  			Src: cty.ObjectVal(map[string]cty.Value{
   941  				"set": cty.SetVal([]cty.Value{
   942  					cty.ObjectVal(map[string]cty.Value{
   943  						"foo": cty.NullVal(cty.String),
   944  					}),
   945  				}),
   946  			}),
   947  			Dst: cty.ObjectVal(map[string]cty.Value{
   948  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   949  					"foo": cty.String,
   950  				}))),
   951  			}),
   952  			// If we're only in a plan, we can't compare sets at all
   953  			Expect: cty.ObjectVal(map[string]cty.Value{
   954  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   955  					"foo": cty.String,
   956  				}))),
   957  			}),
   958  		},
   959  		{
   960  			// The empty map is copied over the null map
   961  			Src: cty.ObjectVal(map[string]cty.Value{
   962  				"map": cty.MapValEmpty(cty.String),
   963  			}),
   964  			Dst: cty.ObjectVal(map[string]cty.Value{
   965  				"map": cty.NullVal(cty.Map(cty.String)),
   966  			}),
   967  			Expect: cty.ObjectVal(map[string]cty.Value{
   968  				"map": cty.MapValEmpty(cty.String),
   969  			}),
   970  			Apply: true,
   971  		},
   972  		{
   973  			// A zero value primitive is copied over a null primitive
   974  			Src: cty.ObjectVal(map[string]cty.Value{
   975  				"string": cty.StringVal(""),
   976  			}),
   977  			Dst: cty.ObjectVal(map[string]cty.Value{
   978  				"string": cty.NullVal(cty.String),
   979  			}),
   980  			Expect: cty.ObjectVal(map[string]cty.Value{
   981  				"string": cty.StringVal(""),
   982  			}),
   983  			Apply: true,
   984  		},
   985  		{
   986  			// Plan primitives are kept
   987  			Src: cty.ObjectVal(map[string]cty.Value{
   988  				"string": cty.NumberIntVal(0),
   989  			}),
   990  			Dst: cty.ObjectVal(map[string]cty.Value{
   991  				"string": cty.NullVal(cty.Number),
   992  			}),
   993  			Expect: cty.ObjectVal(map[string]cty.Value{
   994  				"string": cty.NullVal(cty.Number),
   995  			}),
   996  		},
   997  		{
   998  			// Neither plan nor apply should remove empty strings
   999  			Src: cty.ObjectVal(map[string]cty.Value{
  1000  				"string": cty.StringVal(""),
  1001  			}),
  1002  			Dst: cty.ObjectVal(map[string]cty.Value{
  1003  				"string": cty.NullVal(cty.String),
  1004  			}),
  1005  			Expect: cty.ObjectVal(map[string]cty.Value{
  1006  				"string": cty.StringVal(""),
  1007  			}),
  1008  		},
  1009  		{
  1010  			// Neither plan nor apply should remove empty strings
  1011  			Src: cty.ObjectVal(map[string]cty.Value{
  1012  				"string": cty.StringVal(""),
  1013  			}),
  1014  			Dst: cty.ObjectVal(map[string]cty.Value{
  1015  				"string": cty.NullVal(cty.String),
  1016  			}),
  1017  			Expect: cty.ObjectVal(map[string]cty.Value{
  1018  				"string": cty.StringVal(""),
  1019  			}),
  1020  			Apply: true,
  1021  		},
  1022  		{
  1023  			// The null map is retained, because the src was unknown
  1024  			Src: cty.ObjectVal(map[string]cty.Value{
  1025  				"map": cty.UnknownVal(cty.Map(cty.String)),
  1026  			}),
  1027  			Dst: cty.ObjectVal(map[string]cty.Value{
  1028  				"map": cty.NullVal(cty.Map(cty.String)),
  1029  			}),
  1030  			Expect: cty.ObjectVal(map[string]cty.Value{
  1031  				"map": cty.NullVal(cty.Map(cty.String)),
  1032  			}),
  1033  			Apply: true,
  1034  		},
  1035  		{
  1036  			// the nul set is retained, because the src set contains an unknown value
  1037  			Src: cty.ObjectVal(map[string]cty.Value{
  1038  				"set": cty.SetVal([]cty.Value{
  1039  					cty.ObjectVal(map[string]cty.Value{
  1040  						"foo": cty.UnknownVal(cty.String),
  1041  					}),
  1042  				}),
  1043  			}),
  1044  			Dst: cty.ObjectVal(map[string]cty.Value{
  1045  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1046  					"foo": cty.String,
  1047  				}))),
  1048  			}),
  1049  			Expect: cty.ObjectVal(map[string]cty.Value{
  1050  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1051  					"foo": cty.String,
  1052  				}))),
  1053  			}),
  1054  			Apply: true,
  1055  		},
  1056  		{
  1057  			// Retain don't re-add unexpected planned values in a map
  1058  			Src: cty.ObjectVal(map[string]cty.Value{
  1059  				"map": cty.MapVal(map[string]cty.Value{
  1060  					"a": cty.StringVal("a"),
  1061  					"b": cty.StringVal(""),
  1062  				}),
  1063  			}),
  1064  			Dst: cty.ObjectVal(map[string]cty.Value{
  1065  				"map": cty.MapVal(map[string]cty.Value{
  1066  					"a": cty.StringVal("a"),
  1067  				}),
  1068  			}),
  1069  			Expect: cty.ObjectVal(map[string]cty.Value{
  1070  				"map": cty.MapVal(map[string]cty.Value{
  1071  					"a": cty.StringVal("a"),
  1072  				}),
  1073  			}),
  1074  		},
  1075  		{
  1076  			// Remove extra values after apply
  1077  			Src: cty.ObjectVal(map[string]cty.Value{
  1078  				"map": cty.MapVal(map[string]cty.Value{
  1079  					"a": cty.StringVal("a"),
  1080  					"b": cty.StringVal("b"),
  1081  				}),
  1082  			}),
  1083  			Dst: cty.ObjectVal(map[string]cty.Value{
  1084  				"map": cty.MapVal(map[string]cty.Value{
  1085  					"a": cty.StringVal("a"),
  1086  				}),
  1087  			}),
  1088  			Expect: cty.ObjectVal(map[string]cty.Value{
  1089  				"map": cty.MapVal(map[string]cty.Value{
  1090  					"a": cty.StringVal("a"),
  1091  				}),
  1092  			}),
  1093  			Apply: true,
  1094  		},
  1095  		{
  1096  			Src: cty.ObjectVal(map[string]cty.Value{
  1097  				"a": cty.StringVal("a"),
  1098  			}),
  1099  			Dst: cty.EmptyObjectVal,
  1100  			Expect: cty.ObjectVal(map[string]cty.Value{
  1101  				"a": cty.NullVal(cty.String),
  1102  			}),
  1103  		},
  1104  
  1105  		// a list in an object in a list, going from null to empty
  1106  		{
  1107  			Src: cty.ObjectVal(map[string]cty.Value{
  1108  				"network_interface": cty.ListVal([]cty.Value{
  1109  					cty.ObjectVal(map[string]cty.Value{
  1110  						"network_ip":    cty.UnknownVal(cty.String),
  1111  						"access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))),
  1112  						"address":       cty.NullVal(cty.String),
  1113  						"name":          cty.StringVal("nic0"),
  1114  					})}),
  1115  			}),
  1116  			Dst: cty.ObjectVal(map[string]cty.Value{
  1117  				"network_interface": cty.ListVal([]cty.Value{
  1118  					cty.ObjectVal(map[string]cty.Value{
  1119  						"network_ip":    cty.StringVal("10.128.0.64"),
  1120  						"access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})),
  1121  						"address":       cty.StringVal("address"),
  1122  						"name":          cty.StringVal("nic0"),
  1123  					}),
  1124  				}),
  1125  			}),
  1126  			Expect: cty.ObjectVal(map[string]cty.Value{
  1127  				"network_interface": cty.ListVal([]cty.Value{
  1128  					cty.ObjectVal(map[string]cty.Value{
  1129  						"network_ip":    cty.StringVal("10.128.0.64"),
  1130  						"access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))),
  1131  						"address":       cty.StringVal("address"),
  1132  						"name":          cty.StringVal("nic0"),
  1133  					}),
  1134  				}),
  1135  			}),
  1136  			Apply: true,
  1137  		},
  1138  
  1139  		// a list in an object in a list, going from empty to null
  1140  		{
  1141  			Src: cty.ObjectVal(map[string]cty.Value{
  1142  				"network_interface": cty.ListVal([]cty.Value{
  1143  					cty.ObjectVal(map[string]cty.Value{
  1144  						"network_ip":    cty.UnknownVal(cty.String),
  1145  						"access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})),
  1146  						"address":       cty.NullVal(cty.String),
  1147  						"name":          cty.StringVal("nic0"),
  1148  					})}),
  1149  			}),
  1150  			Dst: cty.ObjectVal(map[string]cty.Value{
  1151  				"network_interface": cty.ListVal([]cty.Value{
  1152  					cty.ObjectVal(map[string]cty.Value{
  1153  						"network_ip":    cty.StringVal("10.128.0.64"),
  1154  						"access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))),
  1155  						"address":       cty.StringVal("address"),
  1156  						"name":          cty.StringVal("nic0"),
  1157  					}),
  1158  				}),
  1159  			}),
  1160  			Expect: cty.ObjectVal(map[string]cty.Value{
  1161  				"network_interface": cty.ListVal([]cty.Value{
  1162  					cty.ObjectVal(map[string]cty.Value{
  1163  						"network_ip":    cty.StringVal("10.128.0.64"),
  1164  						"access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})),
  1165  						"address":       cty.StringVal("address"),
  1166  						"name":          cty.StringVal("nic0"),
  1167  					}),
  1168  				}),
  1169  			}),
  1170  			Apply: true,
  1171  		},
  1172  		// the empty list should be transferred, but the new unknown should not be overridden
  1173  		{
  1174  			Src: cty.ObjectVal(map[string]cty.Value{
  1175  				"network_interface": cty.ListVal([]cty.Value{
  1176  					cty.ObjectVal(map[string]cty.Value{
  1177  						"network_ip":    cty.StringVal("10.128.0.64"),
  1178  						"access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})),
  1179  						"address":       cty.NullVal(cty.String),
  1180  						"name":          cty.StringVal("nic0"),
  1181  					})}),
  1182  			}),
  1183  			Dst: cty.ObjectVal(map[string]cty.Value{
  1184  				"network_interface": cty.ListVal([]cty.Value{
  1185  					cty.ObjectVal(map[string]cty.Value{
  1186  						"network_ip":    cty.UnknownVal(cty.String),
  1187  						"access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))),
  1188  						"address":       cty.StringVal("address"),
  1189  						"name":          cty.StringVal("nic0"),
  1190  					}),
  1191  				}),
  1192  			}),
  1193  			Expect: cty.ObjectVal(map[string]cty.Value{
  1194  				"network_interface": cty.ListVal([]cty.Value{
  1195  					cty.ObjectVal(map[string]cty.Value{
  1196  						"network_ip":    cty.UnknownVal(cty.String),
  1197  						"access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})),
  1198  						"address":       cty.StringVal("address"),
  1199  						"name":          cty.StringVal("nic0"),
  1200  					}),
  1201  				}),
  1202  			}),
  1203  		},
  1204  		{
  1205  			// fix unknowns added to a map
  1206  			Src: cty.ObjectVal(map[string]cty.Value{
  1207  				"map": cty.MapVal(map[string]cty.Value{
  1208  					"a": cty.StringVal("a"),
  1209  					"b": cty.StringVal(""),
  1210  				}),
  1211  			}),
  1212  			Dst: cty.ObjectVal(map[string]cty.Value{
  1213  				"map": cty.MapVal(map[string]cty.Value{
  1214  					"a": cty.StringVal("a"),
  1215  					"b": cty.UnknownVal(cty.String),
  1216  				}),
  1217  			}),
  1218  			Expect: cty.ObjectVal(map[string]cty.Value{
  1219  				"map": cty.MapVal(map[string]cty.Value{
  1220  					"a": cty.StringVal("a"),
  1221  					"b": cty.StringVal(""),
  1222  				}),
  1223  			}),
  1224  		},
  1225  		{
  1226  			// fix unknowns lost from a list
  1227  			Src: cty.ObjectVal(map[string]cty.Value{
  1228  				"top": cty.ListVal([]cty.Value{
  1229  					cty.ObjectVal(map[string]cty.Value{
  1230  						"list": cty.ListVal([]cty.Value{
  1231  							cty.ObjectVal(map[string]cty.Value{
  1232  								"values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
  1233  							}),
  1234  						}),
  1235  					}),
  1236  				}),
  1237  			}),
  1238  			Dst: cty.ObjectVal(map[string]cty.Value{
  1239  				"top": cty.ListVal([]cty.Value{
  1240  					cty.ObjectVal(map[string]cty.Value{
  1241  						"list": cty.ListVal([]cty.Value{
  1242  							cty.ObjectVal(map[string]cty.Value{
  1243  								"values": cty.NullVal(cty.List(cty.String)),
  1244  							}),
  1245  						}),
  1246  					}),
  1247  				}),
  1248  			}),
  1249  			Expect: cty.ObjectVal(map[string]cty.Value{
  1250  				"top": cty.ListVal([]cty.Value{
  1251  					cty.ObjectVal(map[string]cty.Value{
  1252  						"list": cty.ListVal([]cty.Value{
  1253  							cty.ObjectVal(map[string]cty.Value{
  1254  								"values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
  1255  							}),
  1256  						}),
  1257  					}),
  1258  				}),
  1259  			}),
  1260  		},
  1261  		{
  1262  			Src: cty.ObjectVal(map[string]cty.Value{
  1263  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1264  					"list": cty.List(cty.String),
  1265  				}))),
  1266  			}),
  1267  			Dst: cty.ObjectVal(map[string]cty.Value{
  1268  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  1269  					"list": cty.List(cty.String),
  1270  				})),
  1271  			}),
  1272  			Expect: cty.ObjectVal(map[string]cty.Value{
  1273  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  1274  					"list": cty.List(cty.String),
  1275  				})),
  1276  			}),
  1277  		},
  1278  		{
  1279  			Src: cty.ObjectVal(map[string]cty.Value{
  1280  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1281  					"list": cty.List(cty.String),
  1282  				}))),
  1283  			}),
  1284  			Dst: cty.ObjectVal(map[string]cty.Value{
  1285  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  1286  					"list": cty.List(cty.String),
  1287  				})),
  1288  			}),
  1289  			Expect: cty.ObjectVal(map[string]cty.Value{
  1290  				"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1291  					"list": cty.List(cty.String),
  1292  				}))),
  1293  			}),
  1294  			Apply: true,
  1295  		},
  1296  	} {
  1297  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1298  			got := normalizeNullValues(tc.Dst, tc.Src, tc.Apply)
  1299  			if !got.RawEquals(tc.Expect) {
  1300  				t.Fatalf("\nexpected: %#v\ngot:      %#v\n", tc.Expect, got)
  1301  			}
  1302  		})
  1303  	}
  1304  }
  1305  
  1306  func TestValidateNulls(t *testing.T) {
  1307  	for i, tc := range []struct {
  1308  		Cfg cty.Value
  1309  		Err bool
  1310  	}{
  1311  		{
  1312  			Cfg: cty.ObjectVal(map[string]cty.Value{
  1313  				"list": cty.ListVal([]cty.Value{
  1314  					cty.StringVal("string"),
  1315  					cty.NullVal(cty.String),
  1316  				}),
  1317  			}),
  1318  			Err: true,
  1319  		},
  1320  		{
  1321  			Cfg: cty.ObjectVal(map[string]cty.Value{
  1322  				"map": cty.MapVal(map[string]cty.Value{
  1323  					"string": cty.StringVal("string"),
  1324  					"null":   cty.NullVal(cty.String),
  1325  				}),
  1326  			}),
  1327  			Err: false,
  1328  		},
  1329  		{
  1330  			Cfg: cty.ObjectVal(map[string]cty.Value{
  1331  				"object": cty.ObjectVal(map[string]cty.Value{
  1332  					"list": cty.ListVal([]cty.Value{
  1333  						cty.StringVal("string"),
  1334  						cty.NullVal(cty.String),
  1335  					}),
  1336  				}),
  1337  			}),
  1338  			Err: true,
  1339  		},
  1340  		{
  1341  			Cfg: cty.ObjectVal(map[string]cty.Value{
  1342  				"object": cty.ObjectVal(map[string]cty.Value{
  1343  					"list": cty.ListVal([]cty.Value{
  1344  						cty.StringVal("string"),
  1345  						cty.NullVal(cty.String),
  1346  					}),
  1347  					"list2": cty.ListVal([]cty.Value{
  1348  						cty.StringVal("string"),
  1349  						cty.NullVal(cty.String),
  1350  					}),
  1351  				}),
  1352  			}),
  1353  			Err: true,
  1354  		},
  1355  		{
  1356  			Cfg: cty.ObjectVal(map[string]cty.Value{
  1357  				"object": cty.ObjectVal(map[string]cty.Value{
  1358  					"list": cty.SetVal([]cty.Value{
  1359  						cty.StringVal("string"),
  1360  						cty.NullVal(cty.String),
  1361  					}),
  1362  				}),
  1363  			}),
  1364  			Err: true,
  1365  		},
  1366  	} {
  1367  		t.Run(strconv.Itoa(i), func(t *testing.T) {
  1368  			d := validateConfigNulls(tc.Cfg, nil)
  1369  			diags := convert.ProtoToDiagnostics(d)
  1370  			switch {
  1371  			case tc.Err:
  1372  				if !diags.HasErrors() {
  1373  					t.Fatal("expected error")
  1374  				}
  1375  			default:
  1376  				if diags.HasErrors() {
  1377  					t.Fatalf("unexpected error: %q", diags.Err())
  1378  				}
  1379  			}
  1380  		})
  1381  	}
  1382  }