github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/configschema/implied_type_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package configschema
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  func TestBlockImpliedType(t *testing.T) {
    13  	tests := map[string]struct {
    14  		Schema *Block
    15  		Want   cty.Type
    16  	}{
    17  		"nil": {
    18  			nil,
    19  			cty.EmptyObject,
    20  		},
    21  		"empty": {
    22  			&Block{},
    23  			cty.EmptyObject,
    24  		},
    25  		"attributes": {
    26  			&Block{
    27  				Attributes: map[string]*Attribute{
    28  					"optional": {
    29  						Type:     cty.String,
    30  						Optional: true,
    31  					},
    32  					"required": {
    33  						Type:     cty.Number,
    34  						Required: true,
    35  					},
    36  					"computed": {
    37  						Type:     cty.List(cty.Bool),
    38  						Computed: true,
    39  					},
    40  					"optional_computed": {
    41  						Type:     cty.Map(cty.Bool),
    42  						Optional: true,
    43  						Computed: true,
    44  					},
    45  				},
    46  			},
    47  			cty.Object(map[string]cty.Type{
    48  				"optional":          cty.String,
    49  				"required":          cty.Number,
    50  				"computed":          cty.List(cty.Bool),
    51  				"optional_computed": cty.Map(cty.Bool),
    52  			}),
    53  		},
    54  		"blocks": {
    55  			&Block{
    56  				BlockTypes: map[string]*NestedBlock{
    57  					"single": &NestedBlock{
    58  						Nesting: NestingSingle,
    59  						Block: Block{
    60  							Attributes: map[string]*Attribute{
    61  								"foo": {
    62  									Type:     cty.DynamicPseudoType,
    63  									Required: true,
    64  								},
    65  							},
    66  						},
    67  					},
    68  					"list": &NestedBlock{
    69  						Nesting: NestingList,
    70  					},
    71  					"set": &NestedBlock{
    72  						Nesting: NestingSet,
    73  					},
    74  					"map": &NestedBlock{
    75  						Nesting: NestingMap,
    76  					},
    77  				},
    78  			},
    79  			cty.Object(map[string]cty.Type{
    80  				"single": cty.Object(map[string]cty.Type{
    81  					"foo": cty.DynamicPseudoType,
    82  				}),
    83  				"list": cty.List(cty.EmptyObject),
    84  				"set":  cty.Set(cty.EmptyObject),
    85  				"map":  cty.Map(cty.EmptyObject),
    86  			}),
    87  		},
    88  		"deep block nesting": {
    89  			&Block{
    90  				BlockTypes: map[string]*NestedBlock{
    91  					"single": &NestedBlock{
    92  						Nesting: NestingSingle,
    93  						Block: Block{
    94  							BlockTypes: map[string]*NestedBlock{
    95  								"list": &NestedBlock{
    96  									Nesting: NestingList,
    97  									Block: Block{
    98  										BlockTypes: map[string]*NestedBlock{
    99  											"set": &NestedBlock{
   100  												Nesting: NestingSet,
   101  											},
   102  										},
   103  									},
   104  								},
   105  							},
   106  						},
   107  					},
   108  				},
   109  			},
   110  			cty.Object(map[string]cty.Type{
   111  				"single": cty.Object(map[string]cty.Type{
   112  					"list": cty.List(cty.Object(map[string]cty.Type{
   113  						"set": cty.Set(cty.EmptyObject),
   114  					})),
   115  				}),
   116  			}),
   117  		},
   118  		"nested objects with optional attrs": {
   119  			&Block{
   120  				Attributes: map[string]*Attribute{
   121  					"map": {
   122  						Optional: true,
   123  						NestedType: &Object{
   124  							Nesting: NestingMap,
   125  							Attributes: map[string]*Attribute{
   126  								"optional":          {Type: cty.String, Optional: true},
   127  								"required":          {Type: cty.Number, Required: true},
   128  								"computed":          {Type: cty.List(cty.Bool), Computed: true},
   129  								"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   130  							},
   131  						},
   132  					},
   133  				},
   134  			},
   135  			// The ImpliedType from the type-level block should not contain any
   136  			// optional attributes.
   137  			cty.Object(map[string]cty.Type{
   138  				"map": cty.Map(cty.Object(
   139  					map[string]cty.Type{
   140  						"optional":          cty.String,
   141  						"required":          cty.Number,
   142  						"computed":          cty.List(cty.Bool),
   143  						"optional_computed": cty.Map(cty.Bool),
   144  					},
   145  				)),
   146  			}),
   147  		},
   148  	}
   149  
   150  	for name, test := range tests {
   151  		t.Run(name, func(t *testing.T) {
   152  			got := test.Schema.ImpliedType()
   153  			if !got.Equals(test.Want) {
   154  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  func TestBlockContainsSensitive(t *testing.T) {
   161  	tests := map[string]struct {
   162  		Schema *Block
   163  		Want   bool
   164  	}{
   165  		"object contains sensitive": {
   166  			&Block{
   167  				Attributes: map[string]*Attribute{
   168  					"sensitive": {Sensitive: true},
   169  				},
   170  			},
   171  			true,
   172  		},
   173  		"no sensitive attrs": {
   174  			&Block{
   175  				Attributes: map[string]*Attribute{
   176  					"insensitive": {},
   177  				},
   178  			},
   179  			false,
   180  		},
   181  		"nested object contains sensitive": {
   182  			&Block{
   183  				Attributes: map[string]*Attribute{
   184  					"nested": {
   185  						NestedType: &Object{
   186  							Nesting: NestingSingle,
   187  							Attributes: map[string]*Attribute{
   188  								"sensitive": {Sensitive: true},
   189  							},
   190  						},
   191  					},
   192  				},
   193  			},
   194  			true,
   195  		},
   196  		"nested obj, no sensitive attrs": {
   197  			&Block{
   198  				Attributes: map[string]*Attribute{
   199  					"nested": {
   200  						NestedType: &Object{
   201  							Nesting: NestingSingle,
   202  							Attributes: map[string]*Attribute{
   203  								"public": {},
   204  							},
   205  						},
   206  					},
   207  				},
   208  			},
   209  			false,
   210  		},
   211  	}
   212  
   213  	for name, test := range tests {
   214  		t.Run(name, func(t *testing.T) {
   215  			got := test.Schema.ContainsSensitive()
   216  			if got != test.Want {
   217  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   218  			}
   219  		})
   220  	}
   221  
   222  }
   223  
   224  func TestObjectImpliedType(t *testing.T) {
   225  	tests := map[string]struct {
   226  		Schema *Object
   227  		Want   cty.Type
   228  	}{
   229  		"nil": {
   230  			nil,
   231  			cty.EmptyObject,
   232  		},
   233  		"empty": {
   234  			&Object{},
   235  			cty.EmptyObject,
   236  		},
   237  		"attributes": {
   238  			&Object{
   239  				Nesting: NestingSingle,
   240  				Attributes: map[string]*Attribute{
   241  					"optional":          {Type: cty.String, Optional: true},
   242  					"required":          {Type: cty.Number, Required: true},
   243  					"computed":          {Type: cty.List(cty.Bool), Computed: true},
   244  					"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   245  				},
   246  			},
   247  			cty.Object(
   248  				map[string]cty.Type{
   249  					"optional":          cty.String,
   250  					"required":          cty.Number,
   251  					"computed":          cty.List(cty.Bool),
   252  					"optional_computed": cty.Map(cty.Bool),
   253  				},
   254  			),
   255  		},
   256  		"nested attributes": {
   257  			&Object{
   258  				Nesting: NestingSingle,
   259  				Attributes: map[string]*Attribute{
   260  					"nested_type": {
   261  						NestedType: &Object{
   262  							Nesting: NestingSingle,
   263  							Attributes: map[string]*Attribute{
   264  								"optional":          {Type: cty.String, Optional: true},
   265  								"required":          {Type: cty.Number, Required: true},
   266  								"computed":          {Type: cty.List(cty.Bool), Computed: true},
   267  								"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   268  							},
   269  						},
   270  						Optional: true,
   271  					},
   272  				},
   273  			},
   274  			cty.Object(map[string]cty.Type{
   275  				"nested_type": cty.Object(map[string]cty.Type{
   276  					"optional":          cty.String,
   277  					"required":          cty.Number,
   278  					"computed":          cty.List(cty.Bool),
   279  					"optional_computed": cty.Map(cty.Bool),
   280  				}),
   281  			}),
   282  		},
   283  		"nested object-type attributes": {
   284  			&Object{
   285  				Nesting: NestingSingle,
   286  				Attributes: map[string]*Attribute{
   287  					"nested_type": {
   288  						NestedType: &Object{
   289  							Nesting: NestingSingle,
   290  							Attributes: map[string]*Attribute{
   291  								"optional":          {Type: cty.String, Optional: true},
   292  								"required":          {Type: cty.Number, Required: true},
   293  								"computed":          {Type: cty.List(cty.Bool), Computed: true},
   294  								"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   295  								"object": {
   296  									Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   297  										"optional": cty.String,
   298  										"required": cty.Number,
   299  									}, []string{"optional"}),
   300  								},
   301  							},
   302  						},
   303  						Optional: true,
   304  					},
   305  				},
   306  			},
   307  			cty.Object(map[string]cty.Type{
   308  				"nested_type": cty.Object(map[string]cty.Type{
   309  					"optional":          cty.String,
   310  					"required":          cty.Number,
   311  					"computed":          cty.List(cty.Bool),
   312  					"optional_computed": cty.Map(cty.Bool),
   313  					"object":            cty.Object(map[string]cty.Type{"optional": cty.String, "required": cty.Number}),
   314  				}),
   315  			}),
   316  		},
   317  		"NestingList": {
   318  			&Object{
   319  				Nesting: NestingList,
   320  				Attributes: map[string]*Attribute{
   321  					"foo": {Type: cty.String, Optional: true},
   322  				},
   323  			},
   324  			cty.List(cty.Object(map[string]cty.Type{"foo": cty.String})),
   325  		},
   326  		"NestingMap": {
   327  			&Object{
   328  				Nesting: NestingMap,
   329  				Attributes: map[string]*Attribute{
   330  					"foo": {Type: cty.String},
   331  				},
   332  			},
   333  			cty.Map(cty.Object(map[string]cty.Type{"foo": cty.String})),
   334  		},
   335  		"NestingSet": {
   336  			&Object{
   337  				Nesting: NestingSet,
   338  				Attributes: map[string]*Attribute{
   339  					"foo": {Type: cty.String},
   340  				},
   341  			},
   342  			cty.Set(cty.Object(map[string]cty.Type{"foo": cty.String})),
   343  		},
   344  		"deeply nested NestingList": {
   345  			&Object{
   346  				Nesting: NestingList,
   347  				Attributes: map[string]*Attribute{
   348  					"foo": {
   349  						NestedType: &Object{
   350  							Nesting: NestingList,
   351  							Attributes: map[string]*Attribute{
   352  								"bar": {Type: cty.String},
   353  							},
   354  						},
   355  					},
   356  				},
   357  			},
   358  			cty.List(cty.Object(map[string]cty.Type{"foo": cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))})),
   359  		},
   360  	}
   361  
   362  	for name, test := range tests {
   363  		t.Run(name, func(t *testing.T) {
   364  			got := test.Schema.ImpliedType()
   365  			if !got.Equals(test.Want) {
   366  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   367  			}
   368  		})
   369  	}
   370  }
   371  
   372  func TestObjectContainsSensitive(t *testing.T) {
   373  	tests := map[string]struct {
   374  		Schema *Object
   375  		Want   bool
   376  	}{
   377  		"object contains sensitive": {
   378  			&Object{
   379  				Attributes: map[string]*Attribute{
   380  					"sensitive": {Sensitive: true},
   381  				},
   382  			},
   383  			true,
   384  		},
   385  		"no sensitive attrs": {
   386  			&Object{
   387  				Attributes: map[string]*Attribute{
   388  					"insensitive": {},
   389  				},
   390  			},
   391  			false,
   392  		},
   393  		"nested object contains sensitive": {
   394  			&Object{
   395  				Attributes: map[string]*Attribute{
   396  					"nested": {
   397  						NestedType: &Object{
   398  							Nesting: NestingSingle,
   399  							Attributes: map[string]*Attribute{
   400  								"sensitive": {Sensitive: true},
   401  							},
   402  						},
   403  					},
   404  				},
   405  			},
   406  			true,
   407  		},
   408  		"nested obj, no sensitive attrs": {
   409  			&Object{
   410  				Attributes: map[string]*Attribute{
   411  					"nested": {
   412  						NestedType: &Object{
   413  							Nesting: NestingSingle,
   414  							Attributes: map[string]*Attribute{
   415  								"public": {},
   416  							},
   417  						},
   418  					},
   419  				},
   420  			},
   421  			false,
   422  		},
   423  		"several nested objects, one contains sensitive": {
   424  			&Object{
   425  				Attributes: map[string]*Attribute{
   426  					"alpha": {
   427  						NestedType: &Object{
   428  							Nesting: NestingSingle,
   429  							Attributes: map[string]*Attribute{
   430  								"nonsensitive": {},
   431  							},
   432  						},
   433  					},
   434  					"beta": {
   435  						NestedType: &Object{
   436  							Nesting: NestingSingle,
   437  							Attributes: map[string]*Attribute{
   438  								"sensitive": {Sensitive: true},
   439  							},
   440  						},
   441  					},
   442  					"gamma": {
   443  						NestedType: &Object{
   444  							Nesting: NestingSingle,
   445  							Attributes: map[string]*Attribute{
   446  								"nonsensitive": {},
   447  							},
   448  						},
   449  					},
   450  				},
   451  			},
   452  			true,
   453  		},
   454  	}
   455  
   456  	for name, test := range tests {
   457  		t.Run(name, func(t *testing.T) {
   458  			got := test.Schema.ContainsSensitive()
   459  			if got != test.Want {
   460  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   461  			}
   462  		})
   463  	}
   464  
   465  }
   466  
   467  // Nested attribute should return optional object attributes for decoding.
   468  func TestObjectSpecType(t *testing.T) {
   469  	tests := map[string]struct {
   470  		Schema *Object
   471  		Want   cty.Type
   472  	}{
   473  		"attributes": {
   474  			&Object{
   475  				Nesting: NestingSingle,
   476  				Attributes: map[string]*Attribute{
   477  					"optional":          {Type: cty.String, Optional: true},
   478  					"required":          {Type: cty.Number, Required: true},
   479  					"computed":          {Type: cty.List(cty.Bool), Computed: true},
   480  					"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   481  				},
   482  			},
   483  			cty.ObjectWithOptionalAttrs(
   484  				map[string]cty.Type{
   485  					"optional":          cty.String,
   486  					"required":          cty.Number,
   487  					"computed":          cty.List(cty.Bool),
   488  					"optional_computed": cty.Map(cty.Bool),
   489  				},
   490  				[]string{"optional", "computed", "optional_computed"},
   491  			),
   492  		},
   493  		"nested attributes": {
   494  			&Object{
   495  				Nesting: NestingSingle,
   496  				Attributes: map[string]*Attribute{
   497  					"nested_type": {
   498  						NestedType: &Object{
   499  							Nesting: NestingSingle,
   500  							Attributes: map[string]*Attribute{
   501  								"optional":          {Type: cty.String, Optional: true},
   502  								"required":          {Type: cty.Number, Required: true},
   503  								"computed":          {Type: cty.List(cty.Bool), Computed: true},
   504  								"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   505  							},
   506  						},
   507  						Optional: true,
   508  					},
   509  				},
   510  			},
   511  			cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   512  				"nested_type": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   513  					"optional":          cty.String,
   514  					"required":          cty.Number,
   515  					"computed":          cty.List(cty.Bool),
   516  					"optional_computed": cty.Map(cty.Bool),
   517  				}, []string{"optional", "computed", "optional_computed"}),
   518  			}, []string{"nested_type"}),
   519  		},
   520  		"nested object-type attributes": {
   521  			&Object{
   522  				Nesting: NestingSingle,
   523  				Attributes: map[string]*Attribute{
   524  					"nested_type": {
   525  						NestedType: &Object{
   526  							Nesting: NestingSingle,
   527  							Attributes: map[string]*Attribute{
   528  								"optional":          {Type: cty.String, Optional: true},
   529  								"required":          {Type: cty.Number, Required: true},
   530  								"computed":          {Type: cty.List(cty.Bool), Computed: true},
   531  								"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
   532  								"object": {
   533  									Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   534  										"optional": cty.String,
   535  										"required": cty.Number,
   536  									}, []string{"optional"}),
   537  								},
   538  							},
   539  						},
   540  						Optional: true,
   541  					},
   542  				},
   543  			},
   544  			cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   545  				"nested_type": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
   546  					"optional":          cty.String,
   547  					"required":          cty.Number,
   548  					"computed":          cty.List(cty.Bool),
   549  					"optional_computed": cty.Map(cty.Bool),
   550  					"object":            cty.ObjectWithOptionalAttrs(map[string]cty.Type{"optional": cty.String, "required": cty.Number}, []string{"optional"}),
   551  				}, []string{"optional", "computed", "optional_computed"}),
   552  			}, []string{"nested_type"}),
   553  		},
   554  		"NestingList": {
   555  			&Object{
   556  				Nesting: NestingList,
   557  				Attributes: map[string]*Attribute{
   558  					"foo": {Type: cty.String, Optional: true},
   559  				},
   560  			},
   561  			cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{"foo": cty.String}, []string{"foo"})),
   562  		},
   563  		"NestingMap": {
   564  			&Object{
   565  				Nesting: NestingMap,
   566  				Attributes: map[string]*Attribute{
   567  					"foo": {Type: cty.String},
   568  				},
   569  			},
   570  			cty.Map(cty.Object(map[string]cty.Type{"foo": cty.String})),
   571  		},
   572  		"NestingSet": {
   573  			&Object{
   574  				Nesting: NestingSet,
   575  				Attributes: map[string]*Attribute{
   576  					"foo": {Type: cty.String},
   577  				},
   578  			},
   579  			cty.Set(cty.Object(map[string]cty.Type{"foo": cty.String})),
   580  		},
   581  		"deeply nested NestingList": {
   582  			&Object{
   583  				Nesting: NestingList,
   584  				Attributes: map[string]*Attribute{
   585  					"foo": {
   586  						NestedType: &Object{
   587  							Nesting: NestingList,
   588  							Attributes: map[string]*Attribute{
   589  								"bar": {Type: cty.String},
   590  							},
   591  						},
   592  					},
   593  				},
   594  			},
   595  			cty.List(cty.Object(map[string]cty.Type{"foo": cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))})),
   596  		},
   597  	}
   598  
   599  	for name, test := range tests {
   600  		t.Run(name, func(t *testing.T) {
   601  			got := test.Schema.specType()
   602  			if !got.Equals(test.Want) {
   603  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   604  			}
   605  		})
   606  	}
   607  }