github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/validation/validation_test.go (about) 1 package validation 2 3 import ( 4 "regexp" 5 "testing" 6 7 "github.com/hashicorp/terraform/helper/schema" 8 ) 9 10 type testCase struct { 11 val interface{} 12 f schema.SchemaValidateFunc 13 expectedErr *regexp.Regexp 14 } 15 16 func TestValidationAll(t *testing.T) { 17 runTestCases(t, []testCase{ 18 { 19 val: "valid", 20 f: All( 21 StringLenBetween(5, 42), 22 StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), 23 ), 24 }, 25 { 26 val: "foo", 27 f: All( 28 StringLenBetween(5, 42), 29 StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), 30 ), 31 expectedErr: regexp.MustCompile("expected length of [\\w]+ to be in the range \\(5 - 42\\), got foo"), 32 }, 33 { 34 val: "!!!!!", 35 f: All( 36 StringLenBetween(5, 42), 37 StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), 38 ), 39 expectedErr: regexp.MustCompile("value must be alphanumeric"), 40 }, 41 }) 42 } 43 44 func TestValidationAny(t *testing.T) { 45 runTestCases(t, []testCase{ 46 { 47 val: 43, 48 f: Any( 49 IntAtLeast(42), 50 IntAtMost(5), 51 ), 52 }, 53 { 54 val: 4, 55 f: Any( 56 IntAtLeast(42), 57 IntAtMost(5), 58 ), 59 }, 60 { 61 val: 7, 62 f: Any( 63 IntAtLeast(42), 64 IntAtMost(5), 65 ), 66 expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(42\\), got 7"), 67 }, 68 { 69 val: 7, 70 f: Any( 71 IntAtLeast(42), 72 IntAtMost(5), 73 ), 74 expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(5\\), got 7"), 75 }, 76 }) 77 } 78 79 func TestValidationIntBetween(t *testing.T) { 80 runTestCases(t, []testCase{ 81 { 82 val: 1, 83 f: IntBetween(1, 1), 84 }, 85 { 86 val: 1, 87 f: IntBetween(0, 2), 88 }, 89 { 90 val: 1, 91 f: IntBetween(2, 3), 92 expectedErr: regexp.MustCompile("expected [\\w]+ to be in the range \\(2 - 3\\), got 1"), 93 }, 94 { 95 val: "1", 96 f: IntBetween(2, 3), 97 expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"), 98 }, 99 }) 100 } 101 102 func TestValidationIntAtLeast(t *testing.T) { 103 runTestCases(t, []testCase{ 104 { 105 val: 1, 106 f: IntAtLeast(1), 107 }, 108 { 109 val: 1, 110 f: IntAtLeast(0), 111 }, 112 { 113 val: 1, 114 f: IntAtLeast(2), 115 expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(2\\), got 1"), 116 }, 117 { 118 val: "1", 119 f: IntAtLeast(2), 120 expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"), 121 }, 122 }) 123 } 124 125 func TestValidationIntAtMost(t *testing.T) { 126 runTestCases(t, []testCase{ 127 { 128 val: 1, 129 f: IntAtMost(1), 130 }, 131 { 132 val: 1, 133 f: IntAtMost(2), 134 }, 135 { 136 val: 1, 137 f: IntAtMost(0), 138 expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(0\\), got 1"), 139 }, 140 { 141 val: "1", 142 f: IntAtMost(0), 143 expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"), 144 }, 145 }) 146 } 147 148 func TestValidationIntInSlice(t *testing.T) { 149 runTestCases(t, []testCase{ 150 { 151 val: 42, 152 f: IntInSlice([]int{1, 42}), 153 }, 154 { 155 val: 42, 156 f: IntInSlice([]int{10, 20}), 157 expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[10 20\\], got 42"), 158 }, 159 { 160 val: "InvalidValue", 161 f: IntInSlice([]int{10, 20}), 162 expectedErr: regexp.MustCompile("expected type of [\\w]+ to be an integer"), 163 }, 164 }) 165 } 166 167 func TestValidationStringInSlice(t *testing.T) { 168 runTestCases(t, []testCase{ 169 { 170 val: "ValidValue", 171 f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), 172 }, 173 // ignore case 174 { 175 val: "VALIDVALUE", 176 f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, true), 177 }, 178 { 179 val: "VALIDVALUE", 180 f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), 181 expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got VALIDVALUE"), 182 }, 183 { 184 val: "InvalidValue", 185 f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), 186 expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got InvalidValue"), 187 }, 188 { 189 val: 1, 190 f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), 191 expectedErr: regexp.MustCompile("expected type of [\\w]+ to be string"), 192 }, 193 }) 194 } 195 196 func TestValidationStringMatch(t *testing.T) { 197 runTestCases(t, []testCase{ 198 { 199 val: "foobar", 200 f: StringMatch(regexp.MustCompile(".*foo.*"), ""), 201 }, 202 { 203 val: "bar", 204 f: StringMatch(regexp.MustCompile(".*foo.*"), ""), 205 expectedErr: regexp.MustCompile("expected value of [\\w]+ to match regular expression " + regexp.QuoteMeta(`".*foo.*"`)), 206 }, 207 { 208 val: "bar", 209 f: StringMatch(regexp.MustCompile(".*foo.*"), "value must contain foo"), 210 expectedErr: regexp.MustCompile("invalid value for [\\w]+ \\(value must contain foo\\)"), 211 }, 212 }) 213 } 214 215 func TestValidationRegexp(t *testing.T) { 216 runTestCases(t, []testCase{ 217 { 218 val: ".*foo.*", 219 f: ValidateRegexp, 220 }, 221 { 222 val: "foo(bar", 223 f: ValidateRegexp, 224 expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")), 225 }, 226 }) 227 } 228 229 func TestValidationSingleIP(t *testing.T) { 230 runTestCases(t, []testCase{ 231 { 232 val: "172.10.10.10", 233 f: SingleIP(), 234 }, 235 { 236 val: "1.1.1", 237 f: SingleIP(), 238 expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")), 239 }, 240 { 241 val: "1.1.1.0/20", 242 f: SingleIP(), 243 expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")), 244 }, 245 { 246 val: "256.1.1.1", 247 f: SingleIP(), 248 expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")), 249 }, 250 }) 251 } 252 253 func TestValidationIPRange(t *testing.T) { 254 runTestCases(t, []testCase{ 255 { 256 val: "172.10.10.10-172.10.10.12", 257 f: IPRange(), 258 }, 259 { 260 val: "172.10.10.20", 261 f: IPRange(), 262 expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")), 263 }, 264 { 265 val: "172.10.10.20-172.10.10.12", 266 f: IPRange(), 267 expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")), 268 }, 269 }) 270 } 271 272 func TestValidateRFC3339TimeString(t *testing.T) { 273 runTestCases(t, []testCase{ 274 { 275 val: "2018-03-01T00:00:00Z", 276 f: ValidateRFC3339TimeString, 277 }, 278 { 279 val: "2018-03-01T00:00:00-05:00", 280 f: ValidateRFC3339TimeString, 281 }, 282 { 283 val: "2018-03-01T00:00:00+05:00", 284 f: ValidateRFC3339TimeString, 285 }, 286 { 287 val: "03/01/2018", 288 f: ValidateRFC3339TimeString, 289 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 290 }, 291 { 292 val: "03-01-2018", 293 f: ValidateRFC3339TimeString, 294 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 295 }, 296 { 297 val: "2018-03-01", 298 f: ValidateRFC3339TimeString, 299 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 300 }, 301 { 302 val: "2018-03-01T", 303 f: ValidateRFC3339TimeString, 304 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 305 }, 306 { 307 val: "2018-03-01T00:00:00", 308 f: ValidateRFC3339TimeString, 309 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 310 }, 311 { 312 val: "2018-03-01T00:00:00Z05:00", 313 f: ValidateRFC3339TimeString, 314 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 315 }, 316 { 317 val: "2018-03-01T00:00:00Z-05:00", 318 f: ValidateRFC3339TimeString, 319 expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)), 320 }, 321 }) 322 } 323 324 func TestValidateJsonString(t *testing.T) { 325 type testCases struct { 326 Value string 327 ErrCount int 328 } 329 330 invalidCases := []testCases{ 331 { 332 Value: `{0:"1"}`, 333 ErrCount: 1, 334 }, 335 { 336 Value: `{'abc':1}`, 337 ErrCount: 1, 338 }, 339 { 340 Value: `{"def":}`, 341 ErrCount: 1, 342 }, 343 { 344 Value: `{"xyz":[}}`, 345 ErrCount: 1, 346 }, 347 } 348 349 for _, tc := range invalidCases { 350 _, errors := ValidateJsonString(tc.Value, "json") 351 if len(errors) != tc.ErrCount { 352 t.Fatalf("Expected %q to trigger a validation error.", tc.Value) 353 } 354 } 355 356 validCases := []testCases{ 357 { 358 Value: ``, 359 ErrCount: 0, 360 }, 361 { 362 Value: `{}`, 363 ErrCount: 0, 364 }, 365 { 366 Value: `{"abc":["1","2"]}`, 367 ErrCount: 0, 368 }, 369 } 370 371 for _, tc := range validCases { 372 _, errors := ValidateJsonString(tc.Value, "json") 373 if len(errors) != tc.ErrCount { 374 t.Fatalf("Expected %q not to trigger a validation error.", tc.Value) 375 } 376 } 377 } 378 379 func TestValidateListUniqueStrings(t *testing.T) { 380 runTestCases(t, []testCase{ 381 { 382 val: []interface{}{"foo", "bar"}, 383 f: ValidateListUniqueStrings, 384 }, 385 { 386 val: []interface{}{"foo", "bar", "foo"}, 387 f: ValidateListUniqueStrings, 388 expectedErr: regexp.MustCompile("duplicate entry - foo"), 389 }, 390 { 391 val: []interface{}{"foo", "bar", "foo", "baz", "bar"}, 392 f: ValidateListUniqueStrings, 393 expectedErr: regexp.MustCompile("duplicate entry - (?:foo|bar)"), 394 }, 395 }) 396 } 397 398 func TestValidationNoZeroValues(t *testing.T) { 399 runTestCases(t, []testCase{ 400 { 401 val: "foo", 402 f: NoZeroValues, 403 }, 404 { 405 val: 1, 406 f: NoZeroValues, 407 }, 408 { 409 val: float64(1), 410 f: NoZeroValues, 411 }, 412 { 413 val: "", 414 f: NoZeroValues, 415 expectedErr: regexp.MustCompile("must not be empty"), 416 }, 417 { 418 val: 0, 419 f: NoZeroValues, 420 expectedErr: regexp.MustCompile("must not be zero"), 421 }, 422 { 423 val: float64(0), 424 f: NoZeroValues, 425 expectedErr: regexp.MustCompile("must not be zero"), 426 }, 427 }) 428 } 429 430 func runTestCases(t *testing.T, cases []testCase) { 431 matchErr := func(errs []error, r *regexp.Regexp) bool { 432 // err must match one provided 433 for _, err := range errs { 434 if r.MatchString(err.Error()) { 435 return true 436 } 437 } 438 439 return false 440 } 441 442 for i, tc := range cases { 443 _, errs := tc.f(tc.val, "test_property") 444 445 if len(errs) == 0 && tc.expectedErr == nil { 446 continue 447 } 448 449 if len(errs) != 0 && tc.expectedErr == nil { 450 t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) 451 } 452 453 if !matchErr(errs, tc.expectedErr) { 454 t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) 455 } 456 } 457 } 458 459 func TestFloatBetween(t *testing.T) { 460 cases := map[string]struct { 461 Value interface{} 462 ValidateFunc schema.SchemaValidateFunc 463 ExpectValidationErrors bool 464 }{ 465 "accept valid value": { 466 Value: 1.5, 467 ValidateFunc: FloatBetween(1.0, 2.0), 468 ExpectValidationErrors: false, 469 }, 470 "accept valid value inclusive upper bound": { 471 Value: 1.0, 472 ValidateFunc: FloatBetween(0.0, 1.0), 473 ExpectValidationErrors: false, 474 }, 475 "accept valid value inclusive lower bound": { 476 Value: 0.0, 477 ValidateFunc: FloatBetween(0.0, 1.0), 478 ExpectValidationErrors: false, 479 }, 480 "reject out of range value": { 481 Value: -1.0, 482 ValidateFunc: FloatBetween(0.0, 1.0), 483 ExpectValidationErrors: true, 484 }, 485 "reject incorrectly typed value": { 486 Value: 1, 487 ValidateFunc: FloatBetween(0.0, 1.0), 488 ExpectValidationErrors: true, 489 }, 490 } 491 492 for tn, tc := range cases { 493 _, errors := tc.ValidateFunc(tc.Value, tn) 494 if len(errors) > 0 && !tc.ExpectValidationErrors { 495 t.Errorf("%s: unexpected errors %s", tn, errors) 496 } else if len(errors) == 0 && tc.ExpectValidationErrors { 497 t.Errorf("%s: expected errors but got none", tn) 498 } 499 } 500 }