github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/configschema/internal_validate_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  	multierror "github.com/hashicorp/go-multierror"
    12  )
    13  
    14  func TestBlockInternalValidate(t *testing.T) {
    15  	tests := map[string]struct {
    16  		Block *Block
    17  		Errs  []string
    18  	}{
    19  		"empty": {
    20  			&Block{},
    21  			[]string{},
    22  		},
    23  		"valid": {
    24  			&Block{
    25  				Attributes: map[string]*Attribute{
    26  					"foo": {
    27  						Type:     cty.String,
    28  						Required: true,
    29  					},
    30  					"bar": {
    31  						Type:     cty.String,
    32  						Optional: true,
    33  					},
    34  					"baz": {
    35  						Type:     cty.String,
    36  						Computed: true,
    37  					},
    38  					"baz_maybe": {
    39  						Type:     cty.String,
    40  						Optional: true,
    41  						Computed: true,
    42  					},
    43  				},
    44  				BlockTypes: map[string]*NestedBlock{
    45  					"single": {
    46  						Nesting: NestingSingle,
    47  						Block:   Block{},
    48  					},
    49  					"single_required": {
    50  						Nesting:  NestingSingle,
    51  						Block:    Block{},
    52  						MinItems: 1,
    53  						MaxItems: 1,
    54  					},
    55  					"list": {
    56  						Nesting: NestingList,
    57  						Block:   Block{},
    58  					},
    59  					"list_required": {
    60  						Nesting:  NestingList,
    61  						Block:    Block{},
    62  						MinItems: 1,
    63  					},
    64  					"set": {
    65  						Nesting: NestingSet,
    66  						Block:   Block{},
    67  					},
    68  					"set_required": {
    69  						Nesting:  NestingSet,
    70  						Block:    Block{},
    71  						MinItems: 1,
    72  					},
    73  					"map": {
    74  						Nesting: NestingMap,
    75  						Block:   Block{},
    76  					},
    77  				},
    78  			},
    79  			[]string{},
    80  		},
    81  		"attribute with no flags set": {
    82  			&Block{
    83  				Attributes: map[string]*Attribute{
    84  					"foo": {
    85  						Type: cty.String,
    86  					},
    87  				},
    88  			},
    89  			[]string{"foo: must set Optional, Required or Computed"},
    90  		},
    91  		"attribute required and optional": {
    92  			&Block{
    93  				Attributes: map[string]*Attribute{
    94  					"foo": {
    95  						Type:     cty.String,
    96  						Required: true,
    97  						Optional: true,
    98  					},
    99  				},
   100  			},
   101  			[]string{"foo: cannot set both Optional and Required"},
   102  		},
   103  		"attribute required and computed": {
   104  			&Block{
   105  				Attributes: map[string]*Attribute{
   106  					"foo": {
   107  						Type:     cty.String,
   108  						Required: true,
   109  						Computed: true,
   110  					},
   111  				},
   112  			},
   113  			[]string{"foo: cannot set both Computed and Required"},
   114  		},
   115  		"attribute optional and computed": {
   116  			&Block{
   117  				Attributes: map[string]*Attribute{
   118  					"foo": {
   119  						Type:     cty.String,
   120  						Optional: true,
   121  						Computed: true,
   122  					},
   123  				},
   124  			},
   125  			[]string{},
   126  		},
   127  		"attribute with missing type": {
   128  			&Block{
   129  				Attributes: map[string]*Attribute{
   130  					"foo": {
   131  						Optional: true,
   132  					},
   133  				},
   134  			},
   135  			[]string{"foo: either Type or NestedType must be defined"},
   136  		},
   137  		/* FIXME: This caused errors when applied to existing providers (oci)
   138  		and cannot be enforced without coordination.
   139  
   140  		"attribute with invalid name": {&Block{Attributes:
   141  		    map[string]*Attribute{"fooBar": {Type:     cty.String, Optional:
   142  		    true,
   143  		            },
   144  		        },
   145  		    },
   146  		    []string{"fooBar: name may contain only lowercase letters, digits and underscores"},
   147  		},
   148  		*/
   149  		"attribute with invalid NestedType attribute": {
   150  			&Block{
   151  				Attributes: map[string]*Attribute{
   152  					"foo": {
   153  						NestedType: &Object{
   154  							Nesting: NestingSingle,
   155  							Attributes: map[string]*Attribute{
   156  								"foo": {
   157  									Type:     cty.String,
   158  									Required: true,
   159  									Optional: true,
   160  								},
   161  							},
   162  						},
   163  						Optional: true,
   164  					},
   165  				},
   166  			},
   167  			[]string{"foo: cannot set both Optional and Required"},
   168  		},
   169  		"block type with invalid name": {
   170  			&Block{
   171  				BlockTypes: map[string]*NestedBlock{
   172  					"fooBar": {
   173  						Nesting: NestingSingle,
   174  					},
   175  				},
   176  			},
   177  			[]string{"fooBar: name may contain only lowercase letters, digits and underscores"},
   178  		},
   179  		"colliding names": {
   180  			&Block{
   181  				Attributes: map[string]*Attribute{
   182  					"foo": {
   183  						Type:     cty.String,
   184  						Optional: true,
   185  					},
   186  				},
   187  				BlockTypes: map[string]*NestedBlock{
   188  					"foo": {
   189  						Nesting: NestingSingle,
   190  					},
   191  				},
   192  			},
   193  			[]string{"foo: name defined as both attribute and child block type"},
   194  		},
   195  		"nested block with badness": {
   196  			&Block{
   197  				BlockTypes: map[string]*NestedBlock{
   198  					"bad": {
   199  						Nesting: NestingSingle,
   200  						Block: Block{
   201  							Attributes: map[string]*Attribute{
   202  								"nested_bad": {
   203  									Type:     cty.String,
   204  									Required: true,
   205  									Optional: true,
   206  								},
   207  							},
   208  						},
   209  					},
   210  				},
   211  			},
   212  			[]string{"bad.nested_bad: cannot set both Optional and Required"},
   213  		},
   214  		"nested list block with dynamically-typed attribute": {
   215  			&Block{
   216  				BlockTypes: map[string]*NestedBlock{
   217  					"bad": {
   218  						Nesting: NestingList,
   219  						Block: Block{
   220  							Attributes: map[string]*Attribute{
   221  								"nested_bad": {
   222  									Type:     cty.DynamicPseudoType,
   223  									Optional: true,
   224  								},
   225  							},
   226  						},
   227  					},
   228  				},
   229  			},
   230  			[]string{},
   231  		},
   232  		"nested set block with dynamically-typed attribute": {
   233  			&Block{
   234  				BlockTypes: map[string]*NestedBlock{
   235  					"bad": {
   236  						Nesting: NestingSet,
   237  						Block: Block{
   238  							Attributes: map[string]*Attribute{
   239  								"nested_bad": {
   240  									Type:     cty.DynamicPseudoType,
   241  									Optional: true,
   242  								},
   243  							},
   244  						},
   245  					},
   246  				},
   247  			},
   248  			[]string{"bad: NestingSet blocks may not contain attributes of cty.DynamicPseudoType"},
   249  		},
   250  		"nil": {
   251  			nil,
   252  			[]string{"top-level block schema is nil"},
   253  		},
   254  		"nil attr": {
   255  			&Block{
   256  				Attributes: map[string]*Attribute{
   257  					"bad": nil,
   258  				},
   259  			},
   260  			[]string{"bad: attribute schema is nil"},
   261  		},
   262  		"nil block type": {
   263  			&Block{
   264  				BlockTypes: map[string]*NestedBlock{
   265  					"bad": nil,
   266  				},
   267  			},
   268  			[]string{"bad: block schema is nil"},
   269  		},
   270  	}
   271  
   272  	for name, test := range tests {
   273  		t.Run(name, func(t *testing.T) {
   274  			errs := multierrorErrors(test.Block.InternalValidate())
   275  			if got, want := len(errs), len(test.Errs); got != want {
   276  				t.Errorf("wrong number of errors %d; want %d", got, want)
   277  				for _, err := range errs {
   278  					t.Logf("- %s", err.Error())
   279  				}
   280  			} else {
   281  				if len(errs) > 0 {
   282  					for i := range errs {
   283  						if errs[i].Error() != test.Errs[i] {
   284  							t.Errorf("wrong error: got %s, want %s", errs[i].Error(), test.Errs[i])
   285  						}
   286  					}
   287  				}
   288  			}
   289  		})
   290  	}
   291  }
   292  
   293  func multierrorErrors(err error) []error {
   294  	// A function like this should really be part of the multierror package...
   295  
   296  	if err == nil {
   297  		return nil
   298  	}
   299  
   300  	switch terr := err.(type) {
   301  	case *multierror.Error:
   302  		return terr.Errors
   303  	default:
   304  		return []error{err}
   305  	}
   306  }