github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/lang/funcs/defaults_test.go (about)

     1  package funcs
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  )
     9  
    10  func TestDefaults(t *testing.T) {
    11  	tests := []struct {
    12  		Input, Defaults cty.Value
    13  		Want            cty.Value
    14  		WantErr         string
    15  	}{
    16  		{
    17  			Input: cty.ObjectVal(map[string]cty.Value{
    18  				"a": cty.NullVal(cty.String),
    19  			}),
    20  			Defaults: cty.ObjectVal(map[string]cty.Value{
    21  				"a": cty.StringVal("hello"),
    22  			}),
    23  			Want: cty.ObjectVal(map[string]cty.Value{
    24  				"a": cty.StringVal("hello"),
    25  			}),
    26  		},
    27  		{
    28  			Input: cty.ObjectVal(map[string]cty.Value{
    29  				"a": cty.StringVal("hey"),
    30  			}),
    31  			Defaults: cty.ObjectVal(map[string]cty.Value{
    32  				"a": cty.StringVal("hello"),
    33  			}),
    34  			Want: cty.ObjectVal(map[string]cty.Value{
    35  				"a": cty.StringVal("hey"),
    36  			}),
    37  		},
    38  		{
    39  			Input: cty.ObjectVal(map[string]cty.Value{
    40  				"a": cty.NullVal(cty.String),
    41  			}),
    42  			Defaults: cty.ObjectVal(map[string]cty.Value{
    43  				"a": cty.NullVal(cty.String),
    44  			}),
    45  			Want: cty.ObjectVal(map[string]cty.Value{
    46  				"a": cty.NullVal(cty.String),
    47  			}),
    48  		},
    49  		{
    50  			Input: cty.ObjectVal(map[string]cty.Value{
    51  				"a": cty.NullVal(cty.String),
    52  			}),
    53  			Defaults: cty.ObjectVal(map[string]cty.Value{}),
    54  			Want: cty.ObjectVal(map[string]cty.Value{
    55  				"a": cty.NullVal(cty.String),
    56  			}),
    57  		},
    58  		{
    59  			Input: cty.ObjectVal(map[string]cty.Value{}),
    60  			Defaults: cty.ObjectVal(map[string]cty.Value{
    61  				"a": cty.NullVal(cty.String),
    62  			}),
    63  			WantErr: `.a: target type does not expect an attribute named "a"`,
    64  		},
    65  
    66  		{
    67  			Input: cty.ObjectVal(map[string]cty.Value{
    68  				"a": cty.ListVal([]cty.Value{
    69  					cty.NullVal(cty.String),
    70  				}),
    71  			}),
    72  			Defaults: cty.ObjectVal(map[string]cty.Value{
    73  				"a": cty.StringVal("hello"),
    74  			}),
    75  			Want: cty.ObjectVal(map[string]cty.Value{
    76  				"a": cty.ListVal([]cty.Value{
    77  					cty.StringVal("hello"),
    78  				}),
    79  			}),
    80  		},
    81  		{
    82  			Input: cty.ObjectVal(map[string]cty.Value{
    83  				"a": cty.ListVal([]cty.Value{
    84  					cty.NullVal(cty.String),
    85  					cty.StringVal("hey"),
    86  					cty.NullVal(cty.String),
    87  				}),
    88  			}),
    89  			Defaults: cty.ObjectVal(map[string]cty.Value{
    90  				"a": cty.StringVal("hello"),
    91  			}),
    92  			Want: cty.ObjectVal(map[string]cty.Value{
    93  				"a": cty.ListVal([]cty.Value{
    94  					cty.StringVal("hello"),
    95  					cty.StringVal("hey"),
    96  					cty.StringVal("hello"),
    97  				}),
    98  			}),
    99  		},
   100  		{
   101  			// Using defaults with single set elements is a pretty
   102  			// odd thing to do, but this behavior is just here because
   103  			// it generalizes from how we handle collections. It's
   104  			// tested only to ensure it doesn't change accidentally
   105  			// in future.
   106  			Input: cty.ObjectVal(map[string]cty.Value{
   107  				"a": cty.SetVal([]cty.Value{
   108  					cty.NullVal(cty.String),
   109  					cty.StringVal("hey"),
   110  				}),
   111  			}),
   112  			Defaults: cty.ObjectVal(map[string]cty.Value{
   113  				"a": cty.StringVal("hello"),
   114  			}),
   115  			Want: cty.ObjectVal(map[string]cty.Value{
   116  				"a": cty.SetVal([]cty.Value{
   117  					cty.StringVal("hey"),
   118  					cty.StringVal("hello"),
   119  				}),
   120  			}),
   121  		},
   122  		{
   123  			Input: cty.ObjectVal(map[string]cty.Value{
   124  				"a": cty.MapVal(map[string]cty.Value{
   125  					"x": cty.NullVal(cty.String),
   126  					"y": cty.StringVal("hey"),
   127  					"z": cty.NullVal(cty.String),
   128  				}),
   129  			}),
   130  			Defaults: cty.ObjectVal(map[string]cty.Value{
   131  				"a": cty.StringVal("hello"),
   132  			}),
   133  			Want: cty.ObjectVal(map[string]cty.Value{
   134  				"a": cty.MapVal(map[string]cty.Value{
   135  					"x": cty.StringVal("hello"),
   136  					"y": cty.StringVal("hey"),
   137  					"z": cty.StringVal("hello"),
   138  				}),
   139  			}),
   140  		},
   141  		{
   142  			Input: cty.ObjectVal(map[string]cty.Value{
   143  				"a": cty.ListVal([]cty.Value{
   144  					cty.ObjectVal(map[string]cty.Value{
   145  						"b": cty.StringVal("hey"),
   146  					}),
   147  					cty.ObjectVal(map[string]cty.Value{
   148  						"b": cty.NullVal(cty.String),
   149  					}),
   150  					cty.ObjectVal(map[string]cty.Value{
   151  						"b": cty.StringVal("hey"),
   152  					}),
   153  				}),
   154  			}),
   155  			Defaults: cty.ObjectVal(map[string]cty.Value{
   156  				"a": cty.ObjectVal(map[string]cty.Value{
   157  					"b": cty.StringVal("hello"),
   158  				}),
   159  			}),
   160  			Want: cty.ObjectVal(map[string]cty.Value{
   161  				"a": cty.ListVal([]cty.Value{
   162  					cty.ObjectVal(map[string]cty.Value{
   163  						"b": cty.StringVal("hey"),
   164  					}),
   165  					cty.ObjectVal(map[string]cty.Value{
   166  						"b": cty.StringVal("hello"),
   167  					}),
   168  					cty.ObjectVal(map[string]cty.Value{
   169  						"b": cty.StringVal("hey"),
   170  					}),
   171  				}),
   172  			}),
   173  		},
   174  		{
   175  			Input: cty.ListVal([]cty.Value{
   176  				cty.ObjectVal(map[string]cty.Value{
   177  					"b": cty.StringVal("hey"),
   178  				}),
   179  				cty.ObjectVal(map[string]cty.Value{
   180  					"b": cty.NullVal(cty.String),
   181  				}),
   182  				cty.ObjectVal(map[string]cty.Value{
   183  					"b": cty.StringVal("hey"),
   184  				}),
   185  			}),
   186  			Defaults: cty.ObjectVal(map[string]cty.Value{
   187  				"b": cty.StringVal("hello"),
   188  			}),
   189  			Want: cty.ListVal([]cty.Value{
   190  				cty.ObjectVal(map[string]cty.Value{
   191  					"b": cty.StringVal("hey"),
   192  				}),
   193  				cty.ObjectVal(map[string]cty.Value{
   194  					"b": cty.StringVal("hello"),
   195  				}),
   196  				cty.ObjectVal(map[string]cty.Value{
   197  					"b": cty.StringVal("hey"),
   198  				}),
   199  			}),
   200  		},
   201  		{
   202  			Input: cty.ObjectVal(map[string]cty.Value{
   203  				"a": cty.SetVal([]cty.Value{
   204  					cty.ObjectVal(map[string]cty.Value{
   205  						"b": cty.StringVal("boop"),
   206  					}),
   207  					cty.ObjectVal(map[string]cty.Value{
   208  						"b": cty.NullVal(cty.String),
   209  					}),
   210  					cty.ObjectVal(map[string]cty.Value{
   211  						"b": cty.StringVal("hey"),
   212  					}),
   213  				}),
   214  			}),
   215  			Defaults: cty.ObjectVal(map[string]cty.Value{
   216  				"a": cty.ObjectVal(map[string]cty.Value{
   217  					"b": cty.StringVal("hello"),
   218  				}),
   219  			}),
   220  			Want: cty.ObjectVal(map[string]cty.Value{
   221  				"a": cty.SetVal([]cty.Value{
   222  					cty.ObjectVal(map[string]cty.Value{
   223  						"b": cty.StringVal("boop"),
   224  					}),
   225  					cty.ObjectVal(map[string]cty.Value{
   226  						"b": cty.StringVal("hello"),
   227  					}),
   228  					cty.ObjectVal(map[string]cty.Value{
   229  						"b": cty.StringVal("hey"),
   230  					}),
   231  				}),
   232  			}),
   233  		},
   234  		{
   235  			Input: cty.ObjectVal(map[string]cty.Value{
   236  				"a": cty.SetVal([]cty.Value{
   237  					cty.ObjectVal(map[string]cty.Value{
   238  						"b": cty.StringVal("hello"),
   239  					}),
   240  					cty.ObjectVal(map[string]cty.Value{
   241  						"b": cty.NullVal(cty.String),
   242  					}),
   243  				}),
   244  			}),
   245  			Defaults: cty.ObjectVal(map[string]cty.Value{
   246  				"a": cty.ObjectVal(map[string]cty.Value{
   247  					"b": cty.StringVal("hello"),
   248  				}),
   249  			}),
   250  			Want: cty.ObjectVal(map[string]cty.Value{
   251  				"a": cty.SetVal([]cty.Value{
   252  					// After applying defaults, the one with a null value
   253  					// coalesced with the one with a non-null value,
   254  					// and so there's only one left.
   255  					cty.ObjectVal(map[string]cty.Value{
   256  						"b": cty.StringVal("hello"),
   257  					}),
   258  				}),
   259  			}),
   260  		},
   261  		{
   262  			Input: cty.ObjectVal(map[string]cty.Value{
   263  				"a": cty.MapVal(map[string]cty.Value{
   264  					"boop": cty.ObjectVal(map[string]cty.Value{
   265  						"b": cty.StringVal("hey"),
   266  					}),
   267  					"beep": cty.ObjectVal(map[string]cty.Value{
   268  						"b": cty.NullVal(cty.String),
   269  					}),
   270  				}),
   271  			}),
   272  			Defaults: cty.ObjectVal(map[string]cty.Value{
   273  				"a": cty.ObjectVal(map[string]cty.Value{
   274  					"b": cty.StringVal("hello"),
   275  				}),
   276  			}),
   277  			Want: cty.ObjectVal(map[string]cty.Value{
   278  				"a": cty.MapVal(map[string]cty.Value{
   279  					"boop": cty.ObjectVal(map[string]cty.Value{
   280  						"b": cty.StringVal("hey"),
   281  					}),
   282  					"beep": cty.ObjectVal(map[string]cty.Value{
   283  						"b": cty.StringVal("hello"),
   284  					}),
   285  				}),
   286  			}),
   287  		},
   288  		{
   289  			Input: cty.ObjectVal(map[string]cty.Value{
   290  				"a": cty.ListVal([]cty.Value{
   291  					cty.ObjectVal(map[string]cty.Value{
   292  						"b": cty.StringVal("hey"),
   293  					}),
   294  					cty.ObjectVal(map[string]cty.Value{
   295  						"b": cty.NullVal(cty.String),
   296  					}),
   297  					cty.ObjectVal(map[string]cty.Value{
   298  						"b": cty.StringVal("hey"),
   299  					}),
   300  				}),
   301  			}),
   302  			Defaults: cty.ObjectVal(map[string]cty.Value{
   303  				"a": cty.StringVal("hello"),
   304  			}),
   305  			WantErr: `.a: the default value for a collection of an object type must itself be an object type, not string`,
   306  		},
   307  		{
   308  			Input: cty.ObjectVal(map[string]cty.Value{
   309  				"a": cty.ListVal([]cty.Value{
   310  					cty.NullVal(cty.String),
   311  					cty.StringVal("hey"),
   312  					cty.NullVal(cty.String),
   313  				}),
   314  			}),
   315  			Defaults: cty.ObjectVal(map[string]cty.Value{
   316  				// The default value for a list must be a single value
   317  				// of the list's element type which provides defaults
   318  				// for each element separately, so the default for a
   319  				// list of string should be just a single string, not
   320  				// a list of string.
   321  				"a": cty.ListVal([]cty.Value{
   322  					cty.StringVal("hello"),
   323  				}),
   324  			}),
   325  			WantErr: `.a: invalid default value for string: string required`,
   326  		},
   327  		{
   328  			Input: cty.ObjectVal(map[string]cty.Value{
   329  				"a": cty.TupleVal([]cty.Value{
   330  					cty.NullVal(cty.String),
   331  					cty.StringVal("hey"),
   332  					cty.NullVal(cty.String),
   333  				}),
   334  			}),
   335  			Defaults: cty.ObjectVal(map[string]cty.Value{
   336  				"a": cty.StringVal("hello"),
   337  			}),
   338  			WantErr: `.a: the default value for a tuple type must itself be a tuple type, not string`,
   339  		},
   340  		{
   341  			Input: cty.ObjectVal(map[string]cty.Value{
   342  				"a": cty.TupleVal([]cty.Value{
   343  					cty.NullVal(cty.String),
   344  					cty.StringVal("hey"),
   345  					cty.NullVal(cty.String),
   346  				}),
   347  			}),
   348  			Defaults: cty.ObjectVal(map[string]cty.Value{
   349  				"a": cty.TupleVal([]cty.Value{
   350  					cty.StringVal("hello 0"),
   351  					cty.StringVal("hello 1"),
   352  					cty.StringVal("hello 2"),
   353  				}),
   354  			}),
   355  			Want: cty.ObjectVal(map[string]cty.Value{
   356  				"a": cty.TupleVal([]cty.Value{
   357  					cty.StringVal("hello 0"),
   358  					cty.StringVal("hey"),
   359  					cty.StringVal("hello 2"),
   360  				}),
   361  			}),
   362  		},
   363  		{
   364  			// There's no reason to use this function for plain primitive
   365  			// types, because the "default" argument in a variable definition
   366  			// already has the equivalent behavior. This function is only
   367  			// to deal with the situation of a complex-typed variable where
   368  			// only parts of the data structure are optional.
   369  			Input:    cty.NullVal(cty.String),
   370  			Defaults: cty.StringVal("hello"),
   371  			WantErr:  `only object types and collections of object types can have defaults applied`,
   372  		},
   373  		// When applying default values to structural types, null objects or
   374  		// tuples in the input should be passed through.
   375  		{
   376  			Input: cty.ObjectVal(map[string]cty.Value{
   377  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
   378  					"x": cty.String,
   379  					"y": cty.String,
   380  				})),
   381  				"b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
   382  			}),
   383  			Defaults: cty.ObjectVal(map[string]cty.Value{
   384  				"a": cty.ObjectVal(map[string]cty.Value{
   385  					"x": cty.StringVal("hello"),
   386  					"y": cty.StringVal("there"),
   387  				}),
   388  				"b": cty.TupleVal([]cty.Value{
   389  					cty.StringVal("how are"),
   390  					cty.StringVal("you?"),
   391  				}),
   392  			}),
   393  			Want: cty.ObjectVal(map[string]cty.Value{
   394  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
   395  					"x": cty.String,
   396  					"y": cty.String,
   397  				})),
   398  				"b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
   399  			}),
   400  		},
   401  		// When applying default values to structural types, we permit null
   402  		// values in the defaults, and just pass through the input value.
   403  		{
   404  			Input: cty.ObjectVal(map[string]cty.Value{
   405  				"a": cty.ListVal([]cty.Value{
   406  					cty.ObjectVal(map[string]cty.Value{
   407  						"p": cty.StringVal("xyz"),
   408  						"q": cty.StringVal("xyz"),
   409  					}),
   410  				}),
   411  				"b": cty.SetVal([]cty.Value{
   412  					cty.TupleVal([]cty.Value{
   413  						cty.NumberIntVal(0),
   414  						cty.NumberIntVal(2),
   415  					}),
   416  					cty.TupleVal([]cty.Value{
   417  						cty.NumberIntVal(1),
   418  						cty.NumberIntVal(3),
   419  					}),
   420  				}),
   421  				"c": cty.NullVal(cty.String),
   422  			}),
   423  			Defaults: cty.ObjectVal(map[string]cty.Value{
   424  				"c": cty.StringVal("tada"),
   425  			}),
   426  			Want: cty.ObjectVal(map[string]cty.Value{
   427  				"a": cty.ListVal([]cty.Value{
   428  					cty.ObjectVal(map[string]cty.Value{
   429  						"p": cty.StringVal("xyz"),
   430  						"q": cty.StringVal("xyz"),
   431  					}),
   432  				}),
   433  				"b": cty.SetVal([]cty.Value{
   434  					cty.TupleVal([]cty.Value{
   435  						cty.NumberIntVal(0),
   436  						cty.NumberIntVal(2),
   437  					}),
   438  					cty.TupleVal([]cty.Value{
   439  						cty.NumberIntVal(1),
   440  						cty.NumberIntVal(3),
   441  					}),
   442  				}),
   443  				"c": cty.StringVal("tada"),
   444  			}),
   445  		},
   446  		// When applying default values to collection types, null collections in the
   447  		// input should result in empty collections in the output.
   448  		{
   449  			Input: cty.ObjectVal(map[string]cty.Value{
   450  				"a": cty.NullVal(cty.List(cty.String)),
   451  				"b": cty.NullVal(cty.Map(cty.String)),
   452  				"c": cty.NullVal(cty.Set(cty.String)),
   453  			}),
   454  			Defaults: cty.ObjectVal(map[string]cty.Value{
   455  				"a": cty.StringVal("hello"),
   456  				"b": cty.StringVal("hi"),
   457  				"c": cty.StringVal("greetings"),
   458  			}),
   459  			Want: cty.ObjectVal(map[string]cty.Value{
   460  				"a": cty.ListValEmpty(cty.String),
   461  				"b": cty.MapValEmpty(cty.String),
   462  				"c": cty.SetValEmpty(cty.String),
   463  			}),
   464  		},
   465  		// When specifying fallbacks, we allow mismatched primitive attribute
   466  		// types so long as a safe conversion is possible. This means that we
   467  		// can accept number or boolean values for string attributes.
   468  		{
   469  			Input: cty.ObjectVal(map[string]cty.Value{
   470  				"a": cty.NullVal(cty.String),
   471  				"b": cty.NullVal(cty.String),
   472  				"c": cty.NullVal(cty.String),
   473  			}),
   474  			Defaults: cty.ObjectVal(map[string]cty.Value{
   475  				"a": cty.NumberIntVal(5),
   476  				"b": cty.True,
   477  				"c": cty.StringVal("greetings"),
   478  			}),
   479  			Want: cty.ObjectVal(map[string]cty.Value{
   480  				"a": cty.StringVal("5"),
   481  				"b": cty.StringVal("true"),
   482  				"c": cty.StringVal("greetings"),
   483  			}),
   484  		},
   485  		// Fallbacks with mismatched primitive attribute types which do not
   486  		// have safe conversions must not pass the suitable fallback check,
   487  		// even if unsafe conversion would be possible.
   488  		{
   489  			Input: cty.ObjectVal(map[string]cty.Value{
   490  				"a": cty.NullVal(cty.Bool),
   491  			}),
   492  			Defaults: cty.ObjectVal(map[string]cty.Value{
   493  				"a": cty.StringVal("5"),
   494  			}),
   495  			WantErr: ".a: invalid default value for bool: bool required",
   496  		},
   497  	}
   498  
   499  	for _, test := range tests {
   500  		t.Run(fmt.Sprintf("defaults(%#v, %#v)", test.Input, test.Defaults), func(t *testing.T) {
   501  			got, gotErr := Defaults(test.Input, test.Defaults)
   502  
   503  			if test.WantErr != "" {
   504  				if gotErr == nil {
   505  					t.Fatalf("unexpected success\nwant error: %s", test.WantErr)
   506  				}
   507  				if got, want := gotErr.Error(), test.WantErr; got != want {
   508  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   509  				}
   510  				return
   511  			} else if gotErr != nil {
   512  				t.Fatalf("unexpected error\ngot:  %s", gotErr.Error())
   513  			}
   514  
   515  			if !test.Want.RawEquals(got) {
   516  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   517  			}
   518  		})
   519  	}
   520  }