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 }