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 }