kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/configschema/internal_validate_test.go (about)

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