github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/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  		ErrCount int
    15  	}{
    16  		"empty": {
    17  			&Block{},
    18  			0,
    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  			0,
    77  		},
    78  		"attribute with no flags set": {
    79  			&Block{
    80  				Attributes: map[string]*Attribute{
    81  					"foo": {
    82  						Type: cty.String,
    83  					},
    84  				},
    85  			},
    86  			1, // must set one of the flags
    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  			1, // both required and optional
    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  			1, // both required and computed
   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  			0,
   123  		},
   124  		"attribute with missing type": {
   125  			&Block{
   126  				Attributes: map[string]*Attribute{
   127  					"foo": {
   128  						Optional: true,
   129  					},
   130  				},
   131  			},
   132  			1, // Type must be set
   133  		},
   134  		"attribute with invalid name": {
   135  			&Block{
   136  				Attributes: map[string]*Attribute{
   137  					"fooBar": {
   138  						Type:     cty.String,
   139  						Optional: true,
   140  					},
   141  				},
   142  			},
   143  			1, // name may not contain uppercase letters
   144  		},
   145  		"block type with invalid name": {
   146  			&Block{
   147  				BlockTypes: map[string]*NestedBlock{
   148  					"fooBar": {
   149  						Nesting: NestingSingle,
   150  					},
   151  				},
   152  			},
   153  			1, // name may not contain uppercase letters
   154  		},
   155  		"colliding names": {
   156  			&Block{
   157  				Attributes: map[string]*Attribute{
   158  					"foo": {
   159  						Type:     cty.String,
   160  						Optional: true,
   161  					},
   162  				},
   163  				BlockTypes: map[string]*NestedBlock{
   164  					"foo": {
   165  						Nesting: NestingSingle,
   166  					},
   167  				},
   168  			},
   169  			1, // "foo" is defined as both attribute and block type
   170  		},
   171  		"nested block with badness": {
   172  			&Block{
   173  				BlockTypes: map[string]*NestedBlock{
   174  					"bad": {
   175  						Nesting: NestingSingle,
   176  						Block: Block{
   177  							Attributes: map[string]*Attribute{
   178  								"nested_bad": {
   179  									Type:     cty.String,
   180  									Required: true,
   181  									Optional: true,
   182  								},
   183  							},
   184  						},
   185  					},
   186  				},
   187  			},
   188  			1, // nested_bad is both required and optional
   189  		},
   190  		"nested list block with dynamically-typed attribute": {
   191  			&Block{
   192  				BlockTypes: map[string]*NestedBlock{
   193  					"bad": {
   194  						Nesting: NestingList,
   195  						Block: Block{
   196  							Attributes: map[string]*Attribute{
   197  								"nested_bad": {
   198  									Type:     cty.DynamicPseudoType,
   199  									Optional: true,
   200  								},
   201  							},
   202  						},
   203  					},
   204  				},
   205  			},
   206  			0,
   207  		},
   208  		"nested set block with dynamically-typed attribute": {
   209  			&Block{
   210  				BlockTypes: map[string]*NestedBlock{
   211  					"bad": {
   212  						Nesting: NestingSet,
   213  						Block: Block{
   214  							Attributes: map[string]*Attribute{
   215  								"nested_bad": {
   216  									Type:     cty.DynamicPseudoType,
   217  									Optional: true,
   218  								},
   219  							},
   220  						},
   221  					},
   222  				},
   223  			},
   224  			1, // NestingSet blocks may not contain attributes of cty.DynamicPseudoType
   225  		},
   226  		"nil": {
   227  			nil,
   228  			1, // block is nil
   229  		},
   230  		"nil attr": {
   231  			&Block{
   232  				Attributes: map[string]*Attribute{
   233  					"bad": nil,
   234  				},
   235  			},
   236  			1, // attribute schema is nil
   237  		},
   238  		"nil block type": {
   239  			&Block{
   240  				BlockTypes: map[string]*NestedBlock{
   241  					"bad": nil,
   242  				},
   243  			},
   244  			1, // block schema is nil
   245  		},
   246  	}
   247  
   248  	for name, test := range tests {
   249  		t.Run(name, func(t *testing.T) {
   250  			errs := multierrorErrors(test.Block.InternalValidate())
   251  			if got, want := len(errs), test.ErrCount; got != want {
   252  				t.Errorf("wrong number of errors %d; want %d", got, want)
   253  				for _, err := range errs {
   254  					t.Logf("- %s", err.Error())
   255  				}
   256  			}
   257  		})
   258  	}
   259  }
   260  
   261  func multierrorErrors(err error) []error {
   262  	// A function like this should really be part of the multierror package...
   263  
   264  	if err == nil {
   265  		return nil
   266  	}
   267  
   268  	switch terr := err.(type) {
   269  	case *multierror.Error:
   270  		return terr.Errors
   271  	default:
   272  		return []error{err}
   273  	}
   274  }