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 }