github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/rules/terraformrules/terraform_naming_convention_test.go (about) 1 package terraformrules 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "testing" 8 9 hcl "github.com/hashicorp/hcl/v2" 10 "github.com/terraform-linters/tflint/tflint" 11 ) 12 13 // Data blocks 14 func Test_TerraformNamingConventionRule_Data_DefaultEmpty(t *testing.T) { 15 testDataSnakeCase(t, "default config", "format: snake_case", ` 16 rule "terraform_naming_convention" { 17 enabled = true 18 }`) 19 } 20 21 func Test_TerraformNamingConventionRule_Data_DefaultFormat(t *testing.T) { 22 testDataMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 23 rule "terraform_naming_convention" { 24 enabled = true 25 format = "mixed_snake_case" 26 }`) 27 } 28 29 func Test_TerraformNamingConventionRule_Data_DefaultCustom(t *testing.T) { 30 testDataSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 31 rule "terraform_naming_convention" { 32 enabled = true 33 custom = "^[a-z][a-z]*(_[a-z]+)*$" 34 }`) 35 } 36 37 func Test_TerraformNamingConventionRule_Data_DefaultDisabled(t *testing.T) { 38 testDataDisabled(t, `default config (format=null)`, ` 39 rule "terraform_naming_convention" { 40 enabled = true 41 format = "none" 42 }`) 43 } 44 45 func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideFormat(t *testing.T) { 46 testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 47 rule "terraform_naming_convention" { 48 enabled = true 49 format = "mixed_snake_case" 50 51 data { 52 format = "snake_case" 53 } 54 }`) 55 } 56 57 func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideCustom(t *testing.T) { 58 testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 59 rule "terraform_naming_convention" { 60 enabled = true 61 format = "mixed_snake_case" 62 63 data { 64 custom = "^[a-z][a-z]*(_[a-z]+)*$" 65 } 66 }`) 67 } 68 69 func Test_TerraformNamingConventionRule_Data_DefaultCustom_OverrideFormat(t *testing.T) { 70 testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 71 rule "terraform_naming_convention" { 72 enabled = true 73 custom = "^ignored$" 74 75 data { 76 format = "snake_case" 77 } 78 }`) 79 } 80 81 func Test_TerraformNamingConventionRule_Data_DefaultCustom_OverrideCustom(t *testing.T) { 82 testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 83 rule "terraform_naming_convention" { 84 enabled = true 85 custom = "^ignored$" 86 87 data { 88 custom = "^[a-z][a-z]*(_[a-z]+)*$" 89 } 90 }`) 91 } 92 93 func Test_TerraformNamingConventionRule_Data_DefaultDisabled_OverrideFormat(t *testing.T) { 94 testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 95 rule "terraform_naming_convention" { 96 enabled = true 97 format = "none" 98 99 data { 100 format = "snake_case" 101 } 102 }`) 103 } 104 105 func Test_TerraformNamingConventionRule_Data_DefaultDisabled_OverrideCustom(t *testing.T) { 106 testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 107 rule "terraform_naming_convention" { 108 enabled = true 109 format = "none" 110 111 data { 112 custom = "^[a-z][a-z]*(_[a-z]+)*$" 113 } 114 }`) 115 } 116 117 func Test_TerraformNamingConventionRule_Data_DefaultEmpty_OverrideDisabled(t *testing.T) { 118 testDataDisabled(t, `overridden config (format=null)`, ` 119 rule "terraform_naming_convention" { 120 enabled = true 121 122 data { 123 format = "none" 124 } 125 }`) 126 } 127 128 func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideDisabled(t *testing.T) { 129 testDataDisabled(t, `overridden config (format=null)`, ` 130 rule "terraform_naming_convention" { 131 enabled = true 132 format = "snake_case" 133 134 data { 135 format = "none" 136 } 137 }`) 138 } 139 140 func Test_TerraformNamingConventionRule_Data_CustomFormats(t *testing.T) { 141 testDataSnakeCase(t, `default config (custom_format="custom_snake_case")`, "format: Custom Snake Case", ` 142 rule "terraform_naming_convention" { 143 enabled = true 144 format = "custom_snake_case" 145 146 custom_formats = { 147 custom_snake_case = { 148 description = "Custom Snake Case" 149 regex = "^[a-z][a-z0-9]*(_[a-z0-9]+)*$" 150 } 151 } 152 }`) 153 } 154 155 func Test_TerraformNamingConventionRule_Data_CustomFormats_OverridePredefined(t *testing.T) { 156 testDataSnakeCase(t, `default config (custom_format="snake_case")`, "format: Custom Snake Case", ` 157 rule "terraform_naming_convention" { 158 enabled = true 159 format = "snake_case" 160 161 custom_formats = { 162 snake_case = { 163 description = "Custom Snake Case" 164 regex = "^[a-z][a-z0-9]*(_[a-z0-9]+)*$" 165 } 166 } 167 }`) 168 } 169 170 func testDataSnakeCase(t *testing.T, testType string, formatName string, config string) { 171 rule := NewTerraformNamingConventionRule() 172 173 cases := []struct { 174 Name string 175 Content string 176 Config string 177 Expected tflint.Issues 178 }{ 179 { 180 Name: fmt.Sprintf("data: %s - Invalid snake_case with dash", testType), 181 Content: ` 182 data "aws_eip" "dash-name" { 183 }`, 184 Config: config, 185 Expected: tflint.Issues{ 186 { 187 Rule: rule, 188 Message: fmt.Sprintf("data name `dash-name` must match the following %s", formatName), 189 Range: hcl.Range{ 190 Filename: "tests.tf", 191 Start: hcl.Pos{Line: 2, Column: 1}, 192 End: hcl.Pos{Line: 2, Column: 27}, 193 }, 194 }, 195 }, 196 }, 197 { 198 Name: fmt.Sprintf("data: %s - Invalid snake_case with camelCase", testType), 199 Content: ` 200 data "aws_eip" "camelCased" { 201 }`, 202 Config: config, 203 Expected: tflint.Issues{ 204 { 205 Rule: rule, 206 Message: fmt.Sprintf("data name `camelCased` must match the following %s", formatName), 207 Range: hcl.Range{ 208 Filename: "tests.tf", 209 Start: hcl.Pos{Line: 2, Column: 1}, 210 End: hcl.Pos{Line: 2, Column: 28}, 211 }, 212 }, 213 }, 214 }, 215 { 216 Name: fmt.Sprintf("data: %s - Invalid snake_case with double underscore", testType), 217 Content: ` 218 data "aws_eip" "foo__bar" { 219 }`, 220 Config: config, 221 Expected: tflint.Issues{ 222 { 223 Rule: rule, 224 Message: fmt.Sprintf("data name `foo__bar` must match the following %s", formatName), 225 Range: hcl.Range{ 226 Filename: "tests.tf", 227 Start: hcl.Pos{Line: 2, Column: 1}, 228 End: hcl.Pos{Line: 2, Column: 26}, 229 }, 230 }, 231 }, 232 }, 233 { 234 Name: fmt.Sprintf("data: %s - Invalid snake_case with underscore tail", testType), 235 Content: ` 236 data "aws_eip" "foo_bar_" { 237 }`, 238 Config: config, 239 Expected: tflint.Issues{ 240 { 241 Rule: rule, 242 Message: fmt.Sprintf("data name `foo_bar_` must match the following %s", formatName), 243 Range: hcl.Range{ 244 Filename: "tests.tf", 245 Start: hcl.Pos{Line: 2, Column: 1}, 246 End: hcl.Pos{Line: 2, Column: 26}, 247 }, 248 }, 249 }, 250 }, 251 { 252 Name: fmt.Sprintf("data: %s - Invalid snake_case with Mixed_Snake_Case", testType), 253 Content: ` 254 data "aws_eip" "Foo_Bar" { 255 }`, 256 Config: config, 257 Expected: tflint.Issues{ 258 { 259 Rule: rule, 260 Message: fmt.Sprintf("data name `Foo_Bar` must match the following %s", formatName), 261 Range: hcl.Range{ 262 Filename: "tests.tf", 263 Start: hcl.Pos{Line: 2, Column: 1}, 264 End: hcl.Pos{Line: 2, Column: 25}, 265 }, 266 }, 267 }, 268 }, 269 { 270 Name: fmt.Sprintf("data: %s - Valid snake_case", testType), 271 Content: ` 272 data "aws_eip" "foo_bar" { 273 }`, 274 Config: config, 275 Expected: tflint.Issues{}, 276 }, 277 { 278 Name: fmt.Sprintf("data: %s - Valid single word", testType), 279 Content: ` 280 data "aws_eip" "foo" { 281 }`, 282 Config: config, 283 Expected: tflint.Issues{}, 284 }, 285 } 286 287 for _, tc := range cases { 288 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 289 290 if err := rule.Check(runner); err != nil { 291 t.Fatalf("Unexpected error occurred: %s", err) 292 } 293 294 tflint.AssertIssues(t, tc.Expected, runner.Issues) 295 } 296 } 297 298 func testDataMixedSnakeCase(t *testing.T, testType string, config string) { 299 rule := NewTerraformNamingConventionRule() 300 301 cases := []struct { 302 Name string 303 Content string 304 Config string 305 Expected tflint.Issues 306 }{ 307 { 308 Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with dash", testType), 309 Content: ` 310 data "aws_eip" "dash-name" { 311 }`, 312 Config: config, 313 Expected: tflint.Issues{ 314 { 315 Rule: rule, 316 Message: "data name `dash-name` must match the following format: mixed_snake_case", 317 Range: hcl.Range{ 318 Filename: "tests.tf", 319 Start: hcl.Pos{Line: 2, Column: 1}, 320 End: hcl.Pos{Line: 2, Column: 27}, 321 }, 322 }, 323 }, 324 }, 325 { 326 Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with double underscore", testType), 327 Content: ` 328 data "aws_eip" "Foo__Bar" { 329 }`, 330 Config: config, 331 Expected: tflint.Issues{ 332 { 333 Rule: rule, 334 Message: "data name `Foo__Bar` must match the following format: mixed_snake_case", 335 Range: hcl.Range{ 336 Filename: "tests.tf", 337 Start: hcl.Pos{Line: 2, Column: 1}, 338 End: hcl.Pos{Line: 2, Column: 26}, 339 }, 340 }, 341 }, 342 }, 343 { 344 Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with underscore tail", testType), 345 Content: ` 346 data "aws_eip" "Foo_Bar_" { 347 }`, 348 Config: config, 349 Expected: tflint.Issues{ 350 { 351 Rule: rule, 352 Message: "data name `Foo_Bar_` must match the following format: mixed_snake_case", 353 Range: hcl.Range{ 354 Filename: "tests.tf", 355 Start: hcl.Pos{Line: 2, Column: 1}, 356 End: hcl.Pos{Line: 2, Column: 26}, 357 }, 358 }, 359 }, 360 }, 361 { 362 Name: fmt.Sprintf("data: %s - Valid snake_case", testType), 363 Content: ` 364 data "aws_eip" "foo_bar" { 365 }`, 366 Config: config, 367 Expected: tflint.Issues{}, 368 }, 369 { 370 Name: fmt.Sprintf("data: %s - Valid single word", testType), 371 Content: ` 372 data "aws_eip" "foo" { 373 }`, 374 Config: config, 375 Expected: tflint.Issues{}, 376 }, 377 { 378 Name: fmt.Sprintf("data: %s - Valid Mixed_Snake_Case", testType), 379 Content: ` 380 data "aws_eip" "Foo_Bar" { 381 }`, 382 Config: config, 383 Expected: tflint.Issues{}, 384 }, 385 { 386 Name: fmt.Sprintf("data: %s - Valid single word with upper characters", testType), 387 Content: ` 388 data "aws_eip" "foo" { 389 }`, 390 Config: config, 391 Expected: tflint.Issues{}, 392 }, 393 { 394 Name: fmt.Sprintf("data: %s - Valid PascalCase", testType), 395 Content: ` 396 data "aws_eip" "PascalCase" { 397 }`, 398 Config: config, 399 Expected: tflint.Issues{}, 400 }, 401 { 402 Name: fmt.Sprintf("data: %s - Valid camelCase", testType), 403 Content: ` 404 data "aws_eip" "camelCase" { 405 }`, 406 Config: config, 407 Expected: tflint.Issues{}, 408 }, 409 } 410 411 for _, tc := range cases { 412 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 413 414 if err := rule.Check(runner); err != nil { 415 t.Fatalf("Unexpected error occurred: %s", err) 416 } 417 418 tflint.AssertIssues(t, tc.Expected, runner.Issues) 419 } 420 } 421 422 func testDataDisabled(t *testing.T, testType string, config string) { 423 rule := NewTerraformNamingConventionRule() 424 425 cases := []struct { 426 Name string 427 Content string 428 Config string 429 Expected tflint.Issues 430 }{ 431 { 432 Name: fmt.Sprintf("data: %s - Valid mixed_snake_case with dash", testType), 433 Content: ` 434 data "aws_eip" "dash-name" { 435 }`, 436 Config: config, 437 Expected: tflint.Issues{}, 438 }, 439 { 440 Name: fmt.Sprintf("data: %s - Valid snake_case", testType), 441 Content: ` 442 data "aws_eip" "foo_bar" { 443 }`, 444 Config: config, 445 Expected: tflint.Issues{}, 446 }, 447 { 448 Name: fmt.Sprintf("data: %s - Valid single word", testType), 449 Content: ` 450 data "aws_eip" "foo" { 451 }`, 452 Config: config, 453 Expected: tflint.Issues{}, 454 }, 455 { 456 Name: fmt.Sprintf("data: %s - Valid Mixed_Snake_Case", testType), 457 Content: ` 458 data "aws_eip" "Foo_Bar" { 459 }`, 460 Config: config, 461 Expected: tflint.Issues{}, 462 }, 463 { 464 Name: fmt.Sprintf("data: %s - Valid single word upper characters", testType), 465 Content: ` 466 data "aws_eip" "Foo" { 467 }`, 468 Config: config, 469 Expected: tflint.Issues{}, 470 }, 471 { 472 Name: fmt.Sprintf("data: %s - Valid PascalCase", testType), 473 Content: ` 474 data "aws_eip" "PascalCase" { 475 }`, 476 Config: config, 477 Expected: tflint.Issues{}, 478 }, 479 { 480 Name: fmt.Sprintf("data: %s - Valid camelCase", testType), 481 Content: ` 482 data "aws_eip" "camelCase" { 483 }`, 484 Config: config, 485 Expected: tflint.Issues{}, 486 }, 487 } 488 489 for _, tc := range cases { 490 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 491 492 if err := rule.Check(runner); err != nil { 493 t.Fatalf("Unexpected error occurred: %s", err) 494 } 495 496 tflint.AssertIssues(t, tc.Expected, runner.Issues) 497 } 498 } 499 500 // Local values 501 func Test_TerraformNamingConventionRule_Locals_DefaultEmpty(t *testing.T) { 502 testLocalsSnakeCase(t, "default config", "format: snake_case", ` 503 rule "terraform_naming_convention" { 504 enabled = true 505 }`) 506 } 507 508 func Test_TerraformNamingConventionRule_Locals_DefaultFormat(t *testing.T) { 509 testLocalsMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 510 rule "terraform_naming_convention" { 511 enabled = true 512 format = "mixed_snake_case" 513 }`) 514 } 515 516 func Test_TerraformNamingConventionRule_Locals_DefaultCustom(t *testing.T) { 517 testLocalsSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 518 rule "terraform_naming_convention" { 519 enabled = true 520 custom = "^[a-z][a-z]*(_[a-z]+)*$" 521 }`) 522 } 523 524 func Test_TerraformNamingConventionRule_Locals_DefaultDisabled(t *testing.T) { 525 testLocalsDisabled(t, `default config (format=null)`, ` 526 rule "terraform_naming_convention" { 527 enabled = true 528 format = "none" 529 }`) 530 } 531 532 func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideFormat(t *testing.T) { 533 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 534 rule "terraform_naming_convention" { 535 enabled = true 536 format = "mixed_snake_case" 537 538 locals { 539 format = "snake_case" 540 } 541 }`) 542 } 543 544 func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideCustom(t *testing.T) { 545 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 546 rule "terraform_naming_convention" { 547 enabled = true 548 format = "mixed_snake_case" 549 550 locals { 551 custom = "^[a-z][a-z]*(_[a-z]+)*$" 552 } 553 }`) 554 } 555 556 func Test_TerraformNamingConventionRule_Locals_DefaultCustom_OverrideFormat(t *testing.T) { 557 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 558 rule "terraform_naming_convention" { 559 enabled = true 560 custom = "^ignored$" 561 562 locals { 563 format = "snake_case" 564 } 565 }`) 566 } 567 568 func Test_TerraformNamingConventionRule_Locals_DefaultCustom_OverrideCustom(t *testing.T) { 569 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 570 rule "terraform_naming_convention" { 571 enabled = true 572 custom = "^ignored$" 573 574 locals { 575 custom = "^[a-z][a-z]*(_[a-z]+)*$" 576 } 577 }`) 578 } 579 580 func Test_TerraformNamingConventionRule_Locals_DefaultDisabled_OverrideFormat(t *testing.T) { 581 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 582 rule "terraform_naming_convention" { 583 enabled = true 584 format = "none" 585 586 locals { 587 format = "snake_case" 588 } 589 }`) 590 } 591 592 func Test_TerraformNamingConventionRule_Locals_DefaultDisabled_OverrideCustom(t *testing.T) { 593 testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 594 rule "terraform_naming_convention" { 595 enabled = true 596 format = "none" 597 598 locals { 599 custom = "^[a-z][a-z]*(_[a-z]+)*$" 600 } 601 }`) 602 } 603 604 func Test_TerraformNamingConventionRule_Locals_DefaultEmpty_OverrideDisabled(t *testing.T) { 605 testLocalsDisabled(t, `overridden config (format=null)`, ` 606 rule "terraform_naming_convention" { 607 enabled = true 608 609 locals { 610 format = "none" 611 } 612 }`) 613 } 614 615 func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideDisabled(t *testing.T) { 616 testLocalsDisabled(t, `overridden config (format=null)`, ` 617 rule "terraform_naming_convention" { 618 enabled = true 619 format = "snake_case" 620 621 locals { 622 format = "none" 623 } 624 }`) 625 } 626 627 func testLocalsSnakeCase(t *testing.T, testType string, formatName string, config string) { 628 rule := NewTerraformNamingConventionRule() 629 630 cases := []struct { 631 Name string 632 Content string 633 Config string 634 Expected tflint.Issues 635 }{ 636 { 637 Name: fmt.Sprintf("locals: %s - Invalid snake_case with dash", testType), 638 Content: ` 639 locals { 640 dash-name = "invalid" 641 }`, 642 Config: config, 643 Expected: tflint.Issues{ 644 { 645 Rule: rule, 646 Message: fmt.Sprintf("local value name `dash-name` must match the following %s", formatName), 647 Range: hcl.Range{ 648 Filename: "tests.tf", 649 Start: hcl.Pos{Line: 3, Column: 3}, 650 End: hcl.Pos{Line: 3, Column: 24}, 651 }, 652 }, 653 }, 654 }, 655 { 656 Name: fmt.Sprintf("locals: %s - Invalid snake_case with camelCase", testType), 657 Content: ` 658 locals { 659 camelCased = "invalid" 660 }`, 661 Config: config, 662 Expected: tflint.Issues{ 663 { 664 Rule: rule, 665 Message: fmt.Sprintf("local value name `camelCased` must match the following %s", formatName), 666 Range: hcl.Range{ 667 Filename: "tests.tf", 668 Start: hcl.Pos{Line: 3, Column: 3}, 669 End: hcl.Pos{Line: 3, Column: 25}, 670 }, 671 }, 672 }, 673 }, 674 { 675 Name: fmt.Sprintf("locals: %s - Invalid snake_case with double underscore", testType), 676 Content: ` 677 locals { 678 foo__bar = "invalid" 679 }`, 680 Config: config, 681 Expected: tflint.Issues{ 682 { 683 Rule: rule, 684 Message: fmt.Sprintf("local value name `foo__bar` must match the following %s", formatName), 685 Range: hcl.Range{ 686 Filename: "tests.tf", 687 Start: hcl.Pos{Line: 3, Column: 3}, 688 End: hcl.Pos{Line: 3, Column: 23}, 689 }, 690 }, 691 }, 692 }, 693 { 694 Name: fmt.Sprintf("locals: %s - Invalid snake_case with underscore tail", testType), 695 Content: ` 696 locals { 697 foo_bar_ = "invalid" 698 }`, 699 Config: config, 700 Expected: tflint.Issues{ 701 { 702 Rule: rule, 703 Message: fmt.Sprintf("local value name `foo_bar_` must match the following %s", formatName), 704 Range: hcl.Range{ 705 Filename: "tests.tf", 706 Start: hcl.Pos{Line: 3, Column: 3}, 707 End: hcl.Pos{Line: 3, Column: 23}, 708 }, 709 }, 710 }, 711 }, 712 { 713 Name: fmt.Sprintf("locals: %s - Invalid snake_case with Mixed_Snake_Case", testType), 714 Content: ` 715 locals { 716 Foo_Bar = "invalid" 717 }`, 718 Config: config, 719 Expected: tflint.Issues{ 720 { 721 Rule: rule, 722 Message: fmt.Sprintf("local value name `Foo_Bar` must match the following %s", formatName), 723 Range: hcl.Range{ 724 Filename: "tests.tf", 725 Start: hcl.Pos{Line: 3, Column: 3}, 726 End: hcl.Pos{Line: 3, Column: 22}, 727 }, 728 }, 729 }, 730 }, 731 { 732 Name: fmt.Sprintf("locals: %s - Valid snake_case", testType), 733 Content: ` 734 locals { 735 foo_bar = "valid" 736 }`, 737 Config: config, 738 Expected: tflint.Issues{}, 739 }, 740 { 741 Name: fmt.Sprintf("locals: %s - Valid single word", testType), 742 Content: ` 743 locals { 744 foo = "valid" 745 }`, 746 Config: config, 747 Expected: tflint.Issues{}, 748 }, 749 } 750 751 for _, tc := range cases { 752 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 753 754 if err := rule.Check(runner); err != nil { 755 t.Fatalf("Unexpected error occurred: %s", err) 756 } 757 758 tflint.AssertIssues(t, tc.Expected, runner.Issues) 759 } 760 } 761 762 func testLocalsMixedSnakeCase(t *testing.T, testType string, config string) { 763 rule := NewTerraformNamingConventionRule() 764 765 cases := []struct { 766 Name string 767 Content string 768 Config string 769 Expected tflint.Issues 770 }{ 771 { 772 Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with dash", testType), 773 Content: ` 774 locals { 775 dash-name = "invalid" 776 }`, 777 Config: config, 778 Expected: tflint.Issues{ 779 { 780 Rule: rule, 781 Message: "local value name `dash-name` must match the following format: mixed_snake_case", 782 Range: hcl.Range{ 783 Filename: "tests.tf", 784 Start: hcl.Pos{Line: 3, Column: 3}, 785 End: hcl.Pos{Line: 3, Column: 24}, 786 }, 787 }, 788 }, 789 }, 790 { 791 Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with double underscore", testType), 792 Content: ` 793 locals { 794 Foo__Bar = "invalid" 795 }`, 796 Config: config, 797 Expected: tflint.Issues{ 798 { 799 Rule: rule, 800 Message: "local value name `Foo__Bar` must match the following format: mixed_snake_case", 801 Range: hcl.Range{ 802 Filename: "tests.tf", 803 Start: hcl.Pos{Line: 3, Column: 3}, 804 End: hcl.Pos{Line: 3, Column: 23}, 805 }, 806 }, 807 }, 808 }, 809 { 810 Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with underscore tail", testType), 811 Content: ` 812 locals { 813 Foo_Bar_ = "invalid" 814 }`, 815 Config: config, 816 Expected: tflint.Issues{ 817 { 818 Rule: rule, 819 Message: "local value name `Foo_Bar_` must match the following format: mixed_snake_case", 820 Range: hcl.Range{ 821 Filename: "tests.tf", 822 Start: hcl.Pos{Line: 3, Column: 3}, 823 End: hcl.Pos{Line: 3, Column: 23}, 824 }, 825 }, 826 }, 827 }, 828 { 829 Name: fmt.Sprintf("locals: %s - Valid snake_case", testType), 830 Content: ` 831 locals { 832 foo_bar = "valid" 833 }`, 834 Config: config, 835 Expected: tflint.Issues{}, 836 }, 837 { 838 Name: fmt.Sprintf("locals: %s - Valid single word", testType), 839 Content: ` 840 locals { 841 foo = "valid" 842 }`, 843 Config: config, 844 Expected: tflint.Issues{}, 845 }, 846 { 847 Name: fmt.Sprintf("locals: %s - Valid Mixed_Snake_Case", testType), 848 Content: ` 849 locals { 850 Foo_Bar = "valid" 851 }`, 852 Config: config, 853 Expected: tflint.Issues{}, 854 }, 855 { 856 Name: fmt.Sprintf("locals: %s - Valid single word with upper characters", testType), 857 Content: ` 858 locals { 859 Foo = "valid" 860 }`, 861 Config: config, 862 Expected: tflint.Issues{}, 863 }, 864 { 865 Name: fmt.Sprintf("locals: %s - Valid PascalCase", testType), 866 Content: ` 867 locals { 868 PascalCase = "valid" 869 }`, 870 Config: config, 871 Expected: tflint.Issues{}, 872 }, 873 { 874 Name: fmt.Sprintf("locals: %s - Valid camelCase", testType), 875 Content: ` 876 locals { 877 camelCase = "valid" 878 }`, 879 Config: config, 880 Expected: tflint.Issues{}, 881 }, 882 } 883 884 for _, tc := range cases { 885 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 886 887 if err := rule.Check(runner); err != nil { 888 t.Fatalf("Unexpected error occurred: %s", err) 889 } 890 891 tflint.AssertIssues(t, tc.Expected, runner.Issues) 892 } 893 } 894 895 func testLocalsDisabled(t *testing.T, testType string, config string) { 896 rule := NewTerraformNamingConventionRule() 897 898 cases := []struct { 899 Name string 900 Content string 901 Config string 902 Expected tflint.Issues 903 }{ 904 { 905 Name: fmt.Sprintf("locals: %s - Valid mixed_snake_case with dash", testType), 906 Content: ` 907 locals { 908 dash-name = "valid" 909 }`, 910 Config: config, 911 Expected: tflint.Issues{}, 912 }, 913 { 914 Name: fmt.Sprintf("locals: %s - Valid snake_case", testType), 915 Content: ` 916 locals { 917 foo_bar = "valid" 918 }`, 919 Config: config, 920 Expected: tflint.Issues{}, 921 }, 922 { 923 Name: fmt.Sprintf("locals: %s - Valid single word", testType), 924 Content: ` 925 locals { 926 foo = "valid" 927 }`, 928 Config: config, 929 Expected: tflint.Issues{}, 930 }, 931 { 932 Name: fmt.Sprintf("locals: %s - Valid Mixed_Snake_Case", testType), 933 Content: ` 934 locals { 935 Foo_Bar = "valid" 936 }`, 937 Config: config, 938 Expected: tflint.Issues{}, 939 }, 940 { 941 Name: fmt.Sprintf("locals: %s - Valid single word with upper characters", testType), 942 Content: ` 943 locals { 944 Foo = "valid" 945 }`, 946 Config: config, 947 Expected: tflint.Issues{}, 948 }, 949 { 950 Name: fmt.Sprintf("locals: %s - Valid PascalCase", testType), 951 Content: ` 952 locals { 953 PascalCase = "valid" 954 }`, 955 Config: config, 956 Expected: tflint.Issues{}, 957 }, 958 { 959 Name: fmt.Sprintf("locals: %s - Valid camelCase", testType), 960 Content: ` 961 locals { 962 camelCase = "valid" 963 }`, 964 Config: config, 965 Expected: tflint.Issues{}, 966 }, 967 } 968 969 for _, tc := range cases { 970 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 971 972 if err := rule.Check(runner); err != nil { 973 t.Fatalf("Unexpected error occurred: %s", err) 974 } 975 976 tflint.AssertIssues(t, tc.Expected, runner.Issues) 977 } 978 } 979 980 // Module blocks 981 func Test_TerraformNamingConventionRule_Module_DefaultEmpty(t *testing.T) { 982 testModuleSnakeCase(t, "default config", "format: snake_case", ` 983 rule "terraform_naming_convention" { 984 enabled = true 985 }`) 986 } 987 988 func Test_TerraformNamingConventionRule_Module_DefaultFormat(t *testing.T) { 989 testModuleMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 990 rule "terraform_naming_convention" { 991 enabled = true 992 format = "mixed_snake_case" 993 }`) 994 } 995 996 func Test_TerraformNamingConventionRule_Module_DefaultCustom(t *testing.T) { 997 testModuleSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 998 rule "terraform_naming_convention" { 999 enabled = true 1000 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1001 }`) 1002 } 1003 1004 func Test_TerraformNamingConventionRule_Module_DefaultDisabled(t *testing.T) { 1005 testModuleDisabled(t, `default config (format=null)`, ` 1006 rule "terraform_naming_convention" { 1007 enabled = true 1008 format = "none" 1009 }`) 1010 } 1011 1012 func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideFormat(t *testing.T) { 1013 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1014 rule "terraform_naming_convention" { 1015 enabled = true 1016 format = "mixed_snake_case" 1017 1018 module { 1019 format = "snake_case" 1020 } 1021 }`) 1022 } 1023 1024 func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideCustom(t *testing.T) { 1025 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1026 rule "terraform_naming_convention" { 1027 enabled = true 1028 format = "mixed_snake_case" 1029 1030 module { 1031 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1032 } 1033 }`) 1034 } 1035 1036 func Test_TerraformNamingConventionRule_Module_DefaultCustom_OverrideFormat(t *testing.T) { 1037 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1038 rule "terraform_naming_convention" { 1039 enabled = true 1040 custom = "^ignored$" 1041 1042 module { 1043 format = "snake_case" 1044 } 1045 }`) 1046 } 1047 1048 func Test_TerraformNamingConventionRule_Module_DefaultCustom_OverrideCustom(t *testing.T) { 1049 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1050 rule "terraform_naming_convention" { 1051 enabled = true 1052 custom = "^ignored$" 1053 1054 module { 1055 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1056 } 1057 }`) 1058 } 1059 1060 func Test_TerraformNamingConventionRule_Module_DefaultDisabled_OverrideFormat(t *testing.T) { 1061 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1062 rule "terraform_naming_convention" { 1063 enabled = true 1064 format = "none" 1065 1066 module { 1067 format = "snake_case" 1068 } 1069 }`) 1070 } 1071 1072 func Test_TerraformNamingConventionRule_Module_DefaultDisabled_OverrideCustom(t *testing.T) { 1073 testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1074 rule "terraform_naming_convention" { 1075 enabled = true 1076 format = "none" 1077 1078 module { 1079 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1080 } 1081 }`) 1082 } 1083 1084 func Test_TerraformNamingConventionRule_Module_DefaultEmpty_OverrideDisabled(t *testing.T) { 1085 testModuleDisabled(t, `overridden config (format=null)`, ` 1086 rule "terraform_naming_convention" { 1087 enabled = true 1088 1089 module { 1090 format = "none" 1091 } 1092 }`) 1093 } 1094 1095 func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideDisabled(t *testing.T) { 1096 testModuleDisabled(t, `overridden config (format=null)`, ` 1097 rule "terraform_naming_convention" { 1098 enabled = true 1099 format = "snake_case" 1100 1101 module { 1102 format = "none" 1103 } 1104 }`) 1105 } 1106 1107 func testModuleSnakeCase(t *testing.T, testType string, formatName string, config string) { 1108 rule := NewTerraformNamingConventionRule() 1109 1110 cases := []struct { 1111 Name string 1112 Content string 1113 Config string 1114 Expected tflint.Issues 1115 }{ 1116 { 1117 Name: fmt.Sprintf("module: %s - Invalid snake_case with dash", testType), 1118 Content: ` 1119 module "dash-name" { 1120 source = "" 1121 }`, 1122 Config: config, 1123 Expected: tflint.Issues{ 1124 { 1125 Rule: rule, 1126 Message: fmt.Sprintf("module name `dash-name` must match the following %s", formatName), 1127 Range: hcl.Range{ 1128 Filename: "tests.tf", 1129 Start: hcl.Pos{Line: 2, Column: 1}, 1130 End: hcl.Pos{Line: 2, Column: 19}, 1131 }, 1132 }, 1133 }, 1134 }, 1135 { 1136 Name: fmt.Sprintf("module: %s - Invalid snake_case with camelCase", testType), 1137 Content: ` 1138 module "camelCased" { 1139 source = "" 1140 }`, 1141 Config: config, 1142 Expected: tflint.Issues{ 1143 { 1144 Rule: rule, 1145 Message: fmt.Sprintf("module name `camelCased` must match the following %s", formatName), 1146 Range: hcl.Range{ 1147 Filename: "tests.tf", 1148 Start: hcl.Pos{Line: 2, Column: 1}, 1149 End: hcl.Pos{Line: 2, Column: 20}, 1150 }, 1151 }, 1152 }, 1153 }, 1154 { 1155 Name: fmt.Sprintf("module: %s - Invalid snake_case with double underscore", testType), 1156 Content: ` 1157 module "foo__bar" { 1158 source = "" 1159 }`, 1160 Config: config, 1161 Expected: tflint.Issues{ 1162 { 1163 Rule: rule, 1164 Message: fmt.Sprintf("module name `foo__bar` must match the following %s", formatName), 1165 Range: hcl.Range{ 1166 Filename: "tests.tf", 1167 Start: hcl.Pos{Line: 2, Column: 1}, 1168 End: hcl.Pos{Line: 2, Column: 18}, 1169 }, 1170 }, 1171 }, 1172 }, 1173 { 1174 Name: fmt.Sprintf("module: %s - Invalid snake_case with underscore tail", testType), 1175 Content: ` 1176 module "foo_bar_" { 1177 source = "" 1178 }`, 1179 Config: config, 1180 Expected: tflint.Issues{ 1181 { 1182 Rule: rule, 1183 Message: fmt.Sprintf("module name `foo_bar_` must match the following %s", formatName), 1184 Range: hcl.Range{ 1185 Filename: "tests.tf", 1186 Start: hcl.Pos{Line: 2, Column: 1}, 1187 End: hcl.Pos{Line: 2, Column: 18}, 1188 }, 1189 }, 1190 }, 1191 }, 1192 { 1193 Name: fmt.Sprintf("module: %s - Invalid snake_case with Mixed_Snake_Case", testType), 1194 Content: ` 1195 module "Foo_Bar" { 1196 source = "" 1197 }`, 1198 Config: config, 1199 Expected: tflint.Issues{ 1200 { 1201 Rule: rule, 1202 Message: fmt.Sprintf("module name `Foo_Bar` must match the following %s", formatName), 1203 Range: hcl.Range{ 1204 Filename: "tests.tf", 1205 Start: hcl.Pos{Line: 2, Column: 1}, 1206 End: hcl.Pos{Line: 2, Column: 17}, 1207 }, 1208 }, 1209 }, 1210 }, 1211 { 1212 Name: fmt.Sprintf("module: %s - Valid snake_case", testType), 1213 Content: ` 1214 module "foo_bar" { 1215 source = "" 1216 }`, 1217 Config: config, 1218 Expected: tflint.Issues{}, 1219 }, 1220 { 1221 Name: fmt.Sprintf("module: %s - Valid single word", testType), 1222 Content: ` 1223 module "foo" { 1224 source = "" 1225 }`, 1226 Config: config, 1227 Expected: tflint.Issues{}, 1228 }, 1229 } 1230 1231 for _, tc := range cases { 1232 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1233 1234 if err := rule.Check(runner); err != nil { 1235 t.Fatalf("Unexpected error occurred: %s", err) 1236 } 1237 1238 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1239 } 1240 } 1241 1242 func testModuleMixedSnakeCase(t *testing.T, testType string, config string) { 1243 rule := NewTerraformNamingConventionRule() 1244 1245 cases := []struct { 1246 Name string 1247 Content string 1248 Config string 1249 Expected tflint.Issues 1250 }{ 1251 { 1252 Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with dash", testType), 1253 Content: ` 1254 module "dash-name" { 1255 source = "" 1256 }`, 1257 Config: config, 1258 Expected: tflint.Issues{ 1259 { 1260 Rule: rule, 1261 Message: "module name `dash-name` must match the following format: mixed_snake_case", 1262 Range: hcl.Range{ 1263 Filename: "tests.tf", 1264 Start: hcl.Pos{Line: 2, Column: 1}, 1265 End: hcl.Pos{Line: 2, Column: 19}, 1266 }, 1267 }, 1268 }, 1269 }, 1270 { 1271 Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with double underscore", testType), 1272 Content: ` 1273 module "Foo__Bar" { 1274 source = "" 1275 }`, 1276 Config: config, 1277 Expected: tflint.Issues{ 1278 { 1279 Rule: rule, 1280 Message: "module name `Foo__Bar` must match the following format: mixed_snake_case", 1281 Range: hcl.Range{ 1282 Filename: "tests.tf", 1283 Start: hcl.Pos{Line: 2, Column: 1}, 1284 End: hcl.Pos{Line: 2, Column: 18}, 1285 }, 1286 }, 1287 }, 1288 }, 1289 { 1290 Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with underscore tail", testType), 1291 Content: ` 1292 module "Foo_Bar_" { 1293 source = "" 1294 }`, 1295 Config: config, 1296 Expected: tflint.Issues{ 1297 { 1298 Rule: rule, 1299 Message: "module name `Foo_Bar_` must match the following format: mixed_snake_case", 1300 Range: hcl.Range{ 1301 Filename: "tests.tf", 1302 Start: hcl.Pos{Line: 2, Column: 1}, 1303 End: hcl.Pos{Line: 2, Column: 18}, 1304 }, 1305 }, 1306 }, 1307 }, 1308 { 1309 Name: fmt.Sprintf("module: %s - Valid snake_case", testType), 1310 Content: ` 1311 module "foo_bar" { 1312 source = "" 1313 }`, 1314 Config: config, 1315 Expected: tflint.Issues{}, 1316 }, 1317 { 1318 Name: fmt.Sprintf("module: %s - Valid single word", testType), 1319 Content: ` 1320 module "foo" { 1321 source = "" 1322 }`, 1323 Config: config, 1324 Expected: tflint.Issues{}, 1325 }, 1326 { 1327 Name: fmt.Sprintf("module: %s - Valid Mixed_Snake_Case", testType), 1328 Content: ` 1329 module "Foo_Bar" { 1330 source = "" 1331 }`, 1332 Config: config, 1333 Expected: tflint.Issues{}, 1334 }, 1335 { 1336 Name: fmt.Sprintf("module: %s - Valid single word with upper characters", testType), 1337 Content: ` 1338 module "foo" { 1339 source = "" 1340 }`, 1341 Config: config, 1342 Expected: tflint.Issues{}, 1343 }, 1344 { 1345 Name: fmt.Sprintf("module: %s - Valid PascalCase", testType), 1346 Content: ` 1347 module "PascalCase" { 1348 source = "" 1349 }`, 1350 Config: config, 1351 Expected: tflint.Issues{}, 1352 }, 1353 { 1354 Name: fmt.Sprintf("module: %s - Valid camelCase", testType), 1355 Content: ` 1356 module "camelCase" { 1357 source = "" 1358 }`, 1359 Config: config, 1360 Expected: tflint.Issues{}, 1361 }, 1362 } 1363 1364 for _, tc := range cases { 1365 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1366 1367 if err := rule.Check(runner); err != nil { 1368 t.Fatalf("Unexpected error occurred: %s", err) 1369 } 1370 1371 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1372 } 1373 } 1374 1375 func testModuleDisabled(t *testing.T, testType string, config string) { 1376 rule := NewTerraformNamingConventionRule() 1377 1378 cases := []struct { 1379 Name string 1380 Content string 1381 Config string 1382 Expected tflint.Issues 1383 }{ 1384 { 1385 Name: fmt.Sprintf("module: %s - Valid mixed_snake_case with dash", testType), 1386 Content: ` 1387 module "dash-name" { 1388 source = "" 1389 }`, 1390 Config: config, 1391 Expected: tflint.Issues{}, 1392 }, 1393 { 1394 Name: fmt.Sprintf("module: %s - Valid snake_case", testType), 1395 Content: ` 1396 module "foo_bar" { 1397 source = "" 1398 }`, 1399 Config: config, 1400 Expected: tflint.Issues{}, 1401 }, 1402 { 1403 Name: fmt.Sprintf("module: %s - Valid single word", testType), 1404 Content: ` 1405 module "foo" { 1406 source = "" 1407 }`, 1408 Config: config, 1409 Expected: tflint.Issues{}, 1410 }, 1411 { 1412 Name: fmt.Sprintf("module: %s - Valid Mixed_Snake_Case", testType), 1413 Content: ` 1414 module "Foo_Bar" { 1415 source = "" 1416 }`, 1417 Config: config, 1418 Expected: tflint.Issues{}, 1419 }, 1420 { 1421 Name: fmt.Sprintf("module: %s - Valid single word upper characters", testType), 1422 Content: ` 1423 module "Foo" { 1424 source = "" 1425 }`, 1426 Config: config, 1427 Expected: tflint.Issues{}, 1428 }, 1429 { 1430 Name: fmt.Sprintf("module: %s - Valid PascalCase", testType), 1431 Content: ` 1432 module "PascalCase" { 1433 source = "" 1434 }`, 1435 Config: config, 1436 Expected: tflint.Issues{}, 1437 }, 1438 { 1439 Name: fmt.Sprintf("module: %s - Valid camelCase", testType), 1440 Content: ` 1441 module "camelCase" { 1442 source = "" 1443 }`, 1444 Config: config, 1445 Expected: tflint.Issues{}, 1446 }, 1447 } 1448 1449 for _, tc := range cases { 1450 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1451 1452 if err := rule.Check(runner); err != nil { 1453 t.Fatalf("Unexpected error occurred: %s", err) 1454 } 1455 1456 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1457 } 1458 } 1459 1460 // Output blocks 1461 func Test_TerraformNamingConventionRule_Output_DefaultEmpty(t *testing.T) { 1462 testOutputSnakeCase(t, "default config", "format: snake_case", ` 1463 rule "terraform_naming_convention" { 1464 enabled = true 1465 }`) 1466 } 1467 1468 func Test_TerraformNamingConventionRule_Output_DefaultFormat(t *testing.T) { 1469 testOutputMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 1470 rule "terraform_naming_convention" { 1471 enabled = true 1472 format = "mixed_snake_case" 1473 }`) 1474 } 1475 1476 func Test_TerraformNamingConventionRule_Output_DefaultCustom(t *testing.T) { 1477 testOutputSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1478 rule "terraform_naming_convention" { 1479 enabled = true 1480 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1481 }`) 1482 } 1483 1484 func Test_TerraformNamingConventionRule_Output_DefaultDisabled(t *testing.T) { 1485 testOutputDisabled(t, `default config (format=null)`, ` 1486 rule "terraform_naming_convention" { 1487 enabled = true 1488 format = "none" 1489 }`) 1490 } 1491 1492 func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideFormat(t *testing.T) { 1493 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1494 rule "terraform_naming_convention" { 1495 enabled = true 1496 format = "mixed_snake_case" 1497 1498 output { 1499 format = "snake_case" 1500 } 1501 }`) 1502 } 1503 1504 func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideCustom(t *testing.T) { 1505 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1506 rule "terraform_naming_convention" { 1507 enabled = true 1508 format = "mixed_snake_case" 1509 1510 output { 1511 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1512 } 1513 }`) 1514 } 1515 1516 func Test_TerraformNamingConventionRule_Output_DefaultCustom_OverrideFormat(t *testing.T) { 1517 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1518 rule "terraform_naming_convention" { 1519 enabled = true 1520 custom = "^ignored$" 1521 1522 output { 1523 format = "snake_case" 1524 } 1525 }`) 1526 } 1527 1528 func Test_TerraformNamingConventionRule_Output_DefaultCustom_OverrideCustom(t *testing.T) { 1529 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1530 rule "terraform_naming_convention" { 1531 enabled = true 1532 custom = "^ignored$" 1533 1534 output { 1535 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1536 } 1537 }`) 1538 } 1539 1540 func Test_TerraformNamingConventionRule_Output_DefaultDisabled_OverrideFormat(t *testing.T) { 1541 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1542 rule "terraform_naming_convention" { 1543 enabled = true 1544 format = "none" 1545 1546 output { 1547 format = "snake_case" 1548 } 1549 }`) 1550 } 1551 1552 func Test_TerraformNamingConventionRule_Output_DefaultDisabled_OverrideCustom(t *testing.T) { 1553 testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1554 rule "terraform_naming_convention" { 1555 enabled = true 1556 format = "none" 1557 1558 output { 1559 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1560 } 1561 }`) 1562 } 1563 1564 func Test_TerraformNamingConventionRule_Output_DefaultEmpty_OverrideDisabled(t *testing.T) { 1565 testOutputDisabled(t, `overridden config (format=null)`, ` 1566 rule "terraform_naming_convention" { 1567 enabled = true 1568 1569 output { 1570 format = "none" 1571 } 1572 }`) 1573 } 1574 1575 func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideDisabled(t *testing.T) { 1576 testOutputDisabled(t, `overridden config (format=null)`, ` 1577 rule "terraform_naming_convention" { 1578 enabled = true 1579 format = "snake_case" 1580 1581 output { 1582 format = "none" 1583 } 1584 }`) 1585 } 1586 1587 func testOutputSnakeCase(t *testing.T, testType string, formatName string, config string) { 1588 rule := NewTerraformNamingConventionRule() 1589 1590 cases := []struct { 1591 Name string 1592 Content string 1593 Config string 1594 Expected tflint.Issues 1595 }{ 1596 { 1597 Name: fmt.Sprintf("output: %s - Invalid snake_case with dash", testType), 1598 Content: ` 1599 output "dash-name" { 1600 value = "invalid" 1601 }`, 1602 Config: config, 1603 Expected: tflint.Issues{ 1604 { 1605 Rule: rule, 1606 Message: fmt.Sprintf("output name `dash-name` must match the following %s", formatName), 1607 Range: hcl.Range{ 1608 Filename: "tests.tf", 1609 Start: hcl.Pos{Line: 2, Column: 1}, 1610 End: hcl.Pos{Line: 2, Column: 19}, 1611 }, 1612 }, 1613 }, 1614 }, 1615 { 1616 Name: fmt.Sprintf("output: %s - Invalid snake_case with camelCase", testType), 1617 Content: ` 1618 output "camelCased" { 1619 value = "invalid" 1620 }`, 1621 Config: config, 1622 Expected: tflint.Issues{ 1623 { 1624 Rule: rule, 1625 Message: fmt.Sprintf("output name `camelCased` must match the following %s", formatName), 1626 Range: hcl.Range{ 1627 Filename: "tests.tf", 1628 Start: hcl.Pos{Line: 2, Column: 1}, 1629 End: hcl.Pos{Line: 2, Column: 20}, 1630 }, 1631 }, 1632 }, 1633 }, 1634 { 1635 Name: fmt.Sprintf("output: %s - Invalid snake_case with double underscore", testType), 1636 Content: ` 1637 output "foo__bar" { 1638 value = "invalid" 1639 }`, 1640 Config: config, 1641 Expected: tflint.Issues{ 1642 { 1643 Rule: rule, 1644 Message: fmt.Sprintf("output name `foo__bar` must match the following %s", formatName), 1645 Range: hcl.Range{ 1646 Filename: "tests.tf", 1647 Start: hcl.Pos{Line: 2, Column: 1}, 1648 End: hcl.Pos{Line: 2, Column: 18}, 1649 }, 1650 }, 1651 }, 1652 }, 1653 { 1654 Name: fmt.Sprintf("output: %s - Invalid snake_case with underscore tail", testType), 1655 Content: ` 1656 output "foo_bar_" { 1657 value = "invalid" 1658 }`, 1659 Config: config, 1660 Expected: tflint.Issues{ 1661 { 1662 Rule: rule, 1663 Message: fmt.Sprintf("output name `foo_bar_` must match the following %s", formatName), 1664 Range: hcl.Range{ 1665 Filename: "tests.tf", 1666 Start: hcl.Pos{Line: 2, Column: 1}, 1667 End: hcl.Pos{Line: 2, Column: 18}, 1668 }, 1669 }, 1670 }, 1671 }, 1672 { 1673 Name: fmt.Sprintf("output: %s - Invalid snake_case with Mixed_Snake_Case", testType), 1674 Content: ` 1675 output "Foo_Bar" { 1676 value = "invalid" 1677 }`, 1678 Config: config, 1679 Expected: tflint.Issues{ 1680 { 1681 Rule: rule, 1682 Message: fmt.Sprintf("output name `Foo_Bar` must match the following %s", formatName), 1683 Range: hcl.Range{ 1684 Filename: "tests.tf", 1685 Start: hcl.Pos{Line: 2, Column: 1}, 1686 End: hcl.Pos{Line: 2, Column: 17}, 1687 }, 1688 }, 1689 }, 1690 }, 1691 { 1692 Name: fmt.Sprintf("output: %s - Valid snake_case", testType), 1693 Content: ` 1694 output "foo_bar" { 1695 value = "valid" 1696 }`, 1697 Config: config, 1698 Expected: tflint.Issues{}, 1699 }, 1700 { 1701 Name: fmt.Sprintf("output: %s - Valid single word", testType), 1702 Content: ` 1703 output "foo" { 1704 value = "valid" 1705 }`, 1706 Config: config, 1707 Expected: tflint.Issues{}, 1708 }, 1709 } 1710 1711 for _, tc := range cases { 1712 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1713 1714 if err := rule.Check(runner); err != nil { 1715 t.Fatalf("Unexpected error occurred: %s", err) 1716 } 1717 1718 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1719 } 1720 } 1721 1722 func testOutputMixedSnakeCase(t *testing.T, testType string, config string) { 1723 rule := NewTerraformNamingConventionRule() 1724 1725 cases := []struct { 1726 Name string 1727 Content string 1728 Config string 1729 Expected tflint.Issues 1730 }{ 1731 { 1732 Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with dash", testType), 1733 Content: ` 1734 output "dash-name" { 1735 value = "invalid" 1736 }`, 1737 Config: config, 1738 Expected: tflint.Issues{ 1739 { 1740 Rule: rule, 1741 Message: "output name `dash-name` must match the following format: mixed_snake_case", 1742 Range: hcl.Range{ 1743 Filename: "tests.tf", 1744 Start: hcl.Pos{Line: 2, Column: 1}, 1745 End: hcl.Pos{Line: 2, Column: 19}, 1746 }, 1747 }, 1748 }, 1749 }, 1750 { 1751 Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with double underscore", testType), 1752 Content: ` 1753 output "Foo__Bar" { 1754 value = "invalid" 1755 }`, 1756 Config: config, 1757 Expected: tflint.Issues{ 1758 { 1759 Rule: rule, 1760 Message: "output name `Foo__Bar` must match the following format: mixed_snake_case", 1761 Range: hcl.Range{ 1762 Filename: "tests.tf", 1763 Start: hcl.Pos{Line: 2, Column: 1}, 1764 End: hcl.Pos{Line: 2, Column: 18}, 1765 }, 1766 }, 1767 }, 1768 }, 1769 { 1770 Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with underscore tail", testType), 1771 Content: ` 1772 output "Foo_Bar_" { 1773 value = "invalid" 1774 }`, 1775 Config: config, 1776 Expected: tflint.Issues{ 1777 { 1778 Rule: rule, 1779 Message: "output name `Foo_Bar_` must match the following format: mixed_snake_case", 1780 Range: hcl.Range{ 1781 Filename: "tests.tf", 1782 Start: hcl.Pos{Line: 2, Column: 1}, 1783 End: hcl.Pos{Line: 2, Column: 18}, 1784 }, 1785 }, 1786 }, 1787 }, 1788 { 1789 Name: fmt.Sprintf("output: %s - Valid snake_case", testType), 1790 Content: ` 1791 output "foo_bar" { 1792 value = "valid" 1793 }`, 1794 Config: config, 1795 Expected: tflint.Issues{}, 1796 }, 1797 { 1798 Name: fmt.Sprintf("output: %s - Valid single word", testType), 1799 Content: ` 1800 output "foo" { 1801 value = "valid" 1802 }`, 1803 Config: config, 1804 Expected: tflint.Issues{}, 1805 }, 1806 { 1807 Name: fmt.Sprintf("output: %s - Valid Mixed_Snake_Case", testType), 1808 Content: ` 1809 output "Foo_Bar" { 1810 value = "valid" 1811 }`, 1812 Config: config, 1813 Expected: tflint.Issues{}, 1814 }, 1815 { 1816 Name: fmt.Sprintf("output: %s - Valid single word with upper characters", testType), 1817 Content: ` 1818 output "foo" { 1819 value = "valid" 1820 }`, 1821 Config: config, 1822 Expected: tflint.Issues{}, 1823 }, 1824 { 1825 Name: fmt.Sprintf("output: %s - Valid PascalCase", testType), 1826 Content: ` 1827 output "PascalCase" { 1828 value = "valid" 1829 }`, 1830 Config: config, 1831 Expected: tflint.Issues{}, 1832 }, 1833 { 1834 Name: fmt.Sprintf("output: %s - Valid camelCase", testType), 1835 Content: ` 1836 output "camelCase" { 1837 value = "valid" 1838 }`, 1839 Config: config, 1840 Expected: tflint.Issues{}, 1841 }, 1842 } 1843 1844 for _, tc := range cases { 1845 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1846 1847 if err := rule.Check(runner); err != nil { 1848 t.Fatalf("Unexpected error occurred: %s", err) 1849 } 1850 1851 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1852 } 1853 } 1854 1855 func testOutputDisabled(t *testing.T, testType string, config string) { 1856 rule := NewTerraformNamingConventionRule() 1857 1858 cases := []struct { 1859 Name string 1860 Content string 1861 Config string 1862 Expected tflint.Issues 1863 }{ 1864 { 1865 Name: fmt.Sprintf("output: %s - Valid mixed_snake_case with dash", testType), 1866 Content: ` 1867 output "dash-name" { 1868 value = "valid" 1869 }`, 1870 Config: config, 1871 Expected: tflint.Issues{}, 1872 }, 1873 { 1874 Name: fmt.Sprintf("output: %s - Valid snake_case", testType), 1875 Content: ` 1876 output "foo_bar" { 1877 value = "valid" 1878 }`, 1879 Config: config, 1880 Expected: tflint.Issues{}, 1881 }, 1882 { 1883 Name: fmt.Sprintf("output: %s - Valid single word", testType), 1884 Content: ` 1885 output "foo" { 1886 value = "valid" 1887 }`, 1888 Config: config, 1889 Expected: tflint.Issues{}, 1890 }, 1891 { 1892 Name: fmt.Sprintf("output: %s - Valid Mixed_Snake_Case", testType), 1893 Content: ` 1894 output "Foo_Bar" { 1895 value = "valid" 1896 }`, 1897 Config: config, 1898 Expected: tflint.Issues{}, 1899 }, 1900 { 1901 Name: fmt.Sprintf("output: %s - Valid single word upper characters", testType), 1902 Content: ` 1903 output "Foo" { 1904 value = "valid" 1905 }`, 1906 Config: config, 1907 Expected: tflint.Issues{}, 1908 }, 1909 { 1910 Name: fmt.Sprintf("output: %s - Valid PascalCase", testType), 1911 Content: ` 1912 output "PascalCase" { 1913 value = "valid" 1914 }`, 1915 Config: config, 1916 Expected: tflint.Issues{}, 1917 }, 1918 { 1919 Name: fmt.Sprintf("output: %s - Valid camelCase", testType), 1920 Content: ` 1921 output "camelCase" { 1922 value = "valid" 1923 }`, 1924 Config: config, 1925 Expected: tflint.Issues{}, 1926 }, 1927 } 1928 1929 for _, tc := range cases { 1930 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 1931 1932 if err := rule.Check(runner); err != nil { 1933 t.Fatalf("Unexpected error occurred: %s", err) 1934 } 1935 1936 tflint.AssertIssues(t, tc.Expected, runner.Issues) 1937 } 1938 } 1939 1940 // Resource blocks 1941 func Test_TerraformNamingConventionRule_Resource_DefaultEmpty(t *testing.T) { 1942 testResourceSnakeCase(t, "default config", "format: snake_case", ` 1943 rule "terraform_naming_convention" { 1944 enabled = true 1945 }`) 1946 } 1947 1948 func Test_TerraformNamingConventionRule_Resource_DefaultFormat(t *testing.T) { 1949 testResourceMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 1950 rule "terraform_naming_convention" { 1951 enabled = true 1952 format = "mixed_snake_case" 1953 }`) 1954 } 1955 1956 func Test_TerraformNamingConventionRule_Resource_DefaultCustom(t *testing.T) { 1957 testResourceSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1958 rule "terraform_naming_convention" { 1959 enabled = true 1960 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1961 }`) 1962 } 1963 1964 func Test_TerraformNamingConventionRule_Resource_DefaultDisabled(t *testing.T) { 1965 testResourceDisabled(t, `default config (format=null)`, ` 1966 rule "terraform_naming_convention" { 1967 enabled = true 1968 format = "none" 1969 }`) 1970 } 1971 1972 func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideFormat(t *testing.T) { 1973 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1974 rule "terraform_naming_convention" { 1975 enabled = true 1976 format = "mixed_snake_case" 1977 1978 resource { 1979 format = "snake_case" 1980 } 1981 }`) 1982 } 1983 1984 func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideCustom(t *testing.T) { 1985 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 1986 rule "terraform_naming_convention" { 1987 enabled = true 1988 format = "mixed_snake_case" 1989 1990 resource { 1991 custom = "^[a-z][a-z]*(_[a-z]+)*$" 1992 } 1993 }`) 1994 } 1995 1996 func Test_TerraformNamingConventionRule_Resource_DefaultCustom_OverrideFormat(t *testing.T) { 1997 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 1998 rule "terraform_naming_convention" { 1999 enabled = true 2000 custom = "^ignored$" 2001 2002 resource { 2003 format = "snake_case" 2004 } 2005 }`) 2006 } 2007 2008 func Test_TerraformNamingConventionRule_Resource_DefaultCustom_OverrideCustom(t *testing.T) { 2009 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2010 rule "terraform_naming_convention" { 2011 enabled = true 2012 custom = "^ignored$" 2013 2014 resource { 2015 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2016 } 2017 }`) 2018 } 2019 2020 func Test_TerraformNamingConventionRule_Resource_DefaultDisabled_OverrideFormat(t *testing.T) { 2021 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 2022 rule "terraform_naming_convention" { 2023 enabled = true 2024 format = "none" 2025 2026 resource { 2027 format = "snake_case" 2028 } 2029 }`) 2030 } 2031 2032 func Test_TerraformNamingConventionRule_Resource_DefaultDisabled_OverrideCustom(t *testing.T) { 2033 testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2034 rule "terraform_naming_convention" { 2035 enabled = true 2036 format = "none" 2037 2038 resource { 2039 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2040 } 2041 }`) 2042 } 2043 2044 func Test_TerraformNamingConventionRule_Resource_DefaultEmpty_OverrideDisabled(t *testing.T) { 2045 testResourceDisabled(t, `overridden config (format=null)`, ` 2046 rule "terraform_naming_convention" { 2047 enabled = true 2048 2049 resource { 2050 format = "none" 2051 } 2052 }`) 2053 } 2054 2055 func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideDisabled(t *testing.T) { 2056 testResourceDisabled(t, `overridden config (format=null)`, ` 2057 rule "terraform_naming_convention" { 2058 enabled = true 2059 format = "snake_case" 2060 2061 resource { 2062 format = "none" 2063 } 2064 }`) 2065 } 2066 2067 func testResourceSnakeCase(t *testing.T, testType string, formatName string, config string) { 2068 rule := NewTerraformNamingConventionRule() 2069 2070 cases := []struct { 2071 Name string 2072 Content string 2073 Config string 2074 Expected tflint.Issues 2075 }{ 2076 { 2077 Name: fmt.Sprintf("resource: %s - Invalid snake_case with dash", testType), 2078 Content: ` 2079 resource "aws_eip" "dash-name" { 2080 }`, 2081 Config: config, 2082 Expected: tflint.Issues{ 2083 { 2084 Rule: rule, 2085 Message: fmt.Sprintf("resource name `dash-name` must match the following %s", formatName), 2086 Range: hcl.Range{ 2087 Filename: "tests.tf", 2088 Start: hcl.Pos{Line: 2, Column: 1}, 2089 End: hcl.Pos{Line: 2, Column: 31}, 2090 }, 2091 }, 2092 }, 2093 }, 2094 { 2095 Name: fmt.Sprintf("resource: %s - Invalid snake_case with camelCase", testType), 2096 Content: ` 2097 resource "aws_eip" "camelCased" { 2098 }`, 2099 Config: config, 2100 Expected: tflint.Issues{ 2101 { 2102 Rule: rule, 2103 Message: fmt.Sprintf("resource name `camelCased` must match the following %s", formatName), 2104 Range: hcl.Range{ 2105 Filename: "tests.tf", 2106 Start: hcl.Pos{Line: 2, Column: 1}, 2107 End: hcl.Pos{Line: 2, Column: 32}, 2108 }, 2109 }, 2110 }, 2111 }, 2112 { 2113 Name: fmt.Sprintf("resource: %s - Invalid snake_case with double underscore", testType), 2114 Content: ` 2115 resource "aws_eip" "foo__bar" { 2116 }`, 2117 Config: config, 2118 Expected: tflint.Issues{ 2119 { 2120 Rule: rule, 2121 Message: fmt.Sprintf("resource name `foo__bar` must match the following %s", formatName), 2122 Range: hcl.Range{ 2123 Filename: "tests.tf", 2124 Start: hcl.Pos{Line: 2, Column: 1}, 2125 End: hcl.Pos{Line: 2, Column: 30}, 2126 }, 2127 }, 2128 }, 2129 }, 2130 { 2131 Name: fmt.Sprintf("resource: %s - Invalid snake_case with underscore tail", testType), 2132 Content: ` 2133 resource "aws_eip" "foo_bar_" { 2134 }`, 2135 Config: config, 2136 Expected: tflint.Issues{ 2137 { 2138 Rule: rule, 2139 Message: fmt.Sprintf("resource name `foo_bar_` must match the following %s", formatName), 2140 Range: hcl.Range{ 2141 Filename: "tests.tf", 2142 Start: hcl.Pos{Line: 2, Column: 1}, 2143 End: hcl.Pos{Line: 2, Column: 30}, 2144 }, 2145 }, 2146 }, 2147 }, 2148 { 2149 Name: fmt.Sprintf("resource: %s - Invalid snake_case with Mixed_Snake_Case", testType), 2150 Content: ` 2151 resource "aws_eip" "Foo_Bar" { 2152 }`, 2153 Config: config, 2154 Expected: tflint.Issues{ 2155 { 2156 Rule: rule, 2157 Message: fmt.Sprintf("resource name `Foo_Bar` must match the following %s", formatName), 2158 Range: hcl.Range{ 2159 Filename: "tests.tf", 2160 Start: hcl.Pos{Line: 2, Column: 1}, 2161 End: hcl.Pos{Line: 2, Column: 29}, 2162 }, 2163 }, 2164 }, 2165 }, 2166 { 2167 Name: fmt.Sprintf("resource: %s - Valid snake_case", testType), 2168 Content: ` 2169 resource "aws_eip" "foo_bar" { 2170 }`, 2171 Config: config, 2172 Expected: tflint.Issues{}, 2173 }, 2174 { 2175 Name: fmt.Sprintf("resource: %s - Valid single word", testType), 2176 Content: ` 2177 resource "aws_eip" "foo" { 2178 }`, 2179 Config: config, 2180 Expected: tflint.Issues{}, 2181 }, 2182 } 2183 2184 for _, tc := range cases { 2185 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2186 2187 if err := rule.Check(runner); err != nil { 2188 t.Fatalf("Unexpected error occurred: %s", err) 2189 } 2190 2191 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2192 } 2193 } 2194 2195 func testResourceMixedSnakeCase(t *testing.T, testType string, config string) { 2196 rule := NewTerraformNamingConventionRule() 2197 2198 cases := []struct { 2199 Name string 2200 Content string 2201 Config string 2202 Expected tflint.Issues 2203 }{ 2204 { 2205 Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with dash", testType), 2206 Content: ` 2207 resource "aws_eip" "dash-name" { 2208 }`, 2209 Config: config, 2210 Expected: tflint.Issues{ 2211 { 2212 Rule: rule, 2213 Message: "resource name `dash-name` must match the following format: mixed_snake_case", 2214 Range: hcl.Range{ 2215 Filename: "tests.tf", 2216 Start: hcl.Pos{Line: 2, Column: 1}, 2217 End: hcl.Pos{Line: 2, Column: 31}, 2218 }, 2219 }, 2220 }, 2221 }, 2222 { 2223 Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with double underscore", testType), 2224 Content: ` 2225 resource "aws_eip" "Foo__Bar" { 2226 }`, 2227 Config: config, 2228 Expected: tflint.Issues{ 2229 { 2230 Rule: rule, 2231 Message: "resource name `Foo__Bar` must match the following format: mixed_snake_case", 2232 Range: hcl.Range{ 2233 Filename: "tests.tf", 2234 Start: hcl.Pos{Line: 2, Column: 1}, 2235 End: hcl.Pos{Line: 2, Column: 30}, 2236 }, 2237 }, 2238 }, 2239 }, 2240 { 2241 Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with underscore tail", testType), 2242 Content: ` 2243 resource "aws_eip" "Foo_Bar_" { 2244 }`, 2245 Config: config, 2246 Expected: tflint.Issues{ 2247 { 2248 Rule: rule, 2249 Message: "resource name `Foo_Bar_` must match the following format: mixed_snake_case", 2250 Range: hcl.Range{ 2251 Filename: "tests.tf", 2252 Start: hcl.Pos{Line: 2, Column: 1}, 2253 End: hcl.Pos{Line: 2, Column: 30}, 2254 }, 2255 }, 2256 }, 2257 }, 2258 { 2259 Name: fmt.Sprintf("resource: %s - Valid snake_case", testType), 2260 Content: ` 2261 resource "aws_eip" "foo_bar" { 2262 }`, 2263 Config: config, 2264 Expected: tflint.Issues{}, 2265 }, 2266 { 2267 Name: fmt.Sprintf("resource: %s - Valid single word", testType), 2268 Content: ` 2269 resource "aws_eip" "foo" { 2270 }`, 2271 Config: config, 2272 Expected: tflint.Issues{}, 2273 }, 2274 { 2275 Name: fmt.Sprintf("resource: %s - Valid Mixed_Snake_Case", testType), 2276 Content: ` 2277 resource "aws_eip" "Foo_Bar" { 2278 }`, 2279 Config: config, 2280 Expected: tflint.Issues{}, 2281 }, 2282 { 2283 Name: fmt.Sprintf("resource: %s - Valid single word with upper characters", testType), 2284 Content: ` 2285 resource "aws_eip" "foo" { 2286 }`, 2287 Config: config, 2288 Expected: tflint.Issues{}, 2289 }, 2290 { 2291 Name: fmt.Sprintf("resource: %s - Valid PascalCase", testType), 2292 Content: ` 2293 resource "aws_eip" "PascalCase" { 2294 }`, 2295 Config: config, 2296 Expected: tflint.Issues{}, 2297 }, 2298 { 2299 Name: fmt.Sprintf("resource: %s - Valid camelCase", testType), 2300 Content: ` 2301 resource "aws_eip" "camelCase" { 2302 }`, 2303 Config: config, 2304 Expected: tflint.Issues{}, 2305 }, 2306 } 2307 2308 for _, tc := range cases { 2309 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2310 2311 if err := rule.Check(runner); err != nil { 2312 t.Fatalf("Unexpected error occurred: %s", err) 2313 } 2314 2315 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2316 } 2317 } 2318 2319 func testResourceDisabled(t *testing.T, testType string, config string) { 2320 rule := NewTerraformNamingConventionRule() 2321 2322 cases := []struct { 2323 Name string 2324 Content string 2325 Config string 2326 Expected tflint.Issues 2327 }{ 2328 { 2329 Name: fmt.Sprintf("resource: %s - Valid mixed_snake_case with dash", testType), 2330 Content: ` 2331 resource "aws_eip" "dash-name" { 2332 }`, 2333 Config: config, 2334 Expected: tflint.Issues{}, 2335 }, 2336 { 2337 Name: fmt.Sprintf("resource: %s - Valid snake_case", testType), 2338 Content: ` 2339 resource "aws_eip" "foo_bar" { 2340 }`, 2341 Config: config, 2342 Expected: tflint.Issues{}, 2343 }, 2344 { 2345 Name: fmt.Sprintf("resource: %s - Valid single word", testType), 2346 Content: ` 2347 resource "aws_eip" "foo" { 2348 }`, 2349 Config: config, 2350 Expected: tflint.Issues{}, 2351 }, 2352 { 2353 Name: fmt.Sprintf("resource: %s - Valid Mixed_Snake_Case", testType), 2354 Content: ` 2355 resource "aws_eip" "Foo_Bar" { 2356 }`, 2357 Config: config, 2358 Expected: tflint.Issues{}, 2359 }, 2360 { 2361 Name: fmt.Sprintf("resource: %s - Valid single word upper characters", testType), 2362 Content: ` 2363 resource "aws_eip" "Foo" { 2364 }`, 2365 Config: config, 2366 Expected: tflint.Issues{}, 2367 }, 2368 { 2369 Name: fmt.Sprintf("resource: %s - Valid PascalCase", testType), 2370 Content: ` 2371 resource "aws_eip" "PascalCase" { 2372 }`, 2373 Config: config, 2374 Expected: tflint.Issues{}, 2375 }, 2376 { 2377 Name: fmt.Sprintf("resource: %s - Valid camelCase", testType), 2378 Content: ` 2379 resource "aws_eip" "camelCase" { 2380 }`, 2381 Config: config, 2382 Expected: tflint.Issues{}, 2383 }, 2384 } 2385 2386 for _, tc := range cases { 2387 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2388 2389 if err := rule.Check(runner); err != nil { 2390 t.Fatalf("Unexpected error occurred: %s", err) 2391 } 2392 2393 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2394 } 2395 } 2396 2397 // Variable blocks 2398 func Test_TerraformNamingConventionRule_Variable_DefaultEmpty(t *testing.T) { 2399 testVariableSnakeCase(t, "default config", "format: snake_case", ` 2400 rule "terraform_naming_convention" { 2401 enabled = true 2402 }`) 2403 } 2404 2405 func Test_TerraformNamingConventionRule_Variable_DefaultFormat(t *testing.T) { 2406 testVariableMixedSnakeCase(t, `default config (format="mixed_snake_case")`, ` 2407 rule "terraform_naming_convention" { 2408 enabled = true 2409 format = "mixed_snake_case" 2410 }`) 2411 } 2412 2413 func Test_TerraformNamingConventionRule_Variable_DefaultCustom(t *testing.T) { 2414 testVariableSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2415 rule "terraform_naming_convention" { 2416 enabled = true 2417 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2418 }`) 2419 } 2420 2421 func Test_TerraformNamingConventionRule_Variable_DefaultDisabled(t *testing.T) { 2422 testVariableDisabled(t, `default config (format=null)`, ` 2423 rule "terraform_naming_convention" { 2424 enabled = true 2425 format = "none" 2426 }`) 2427 } 2428 2429 func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideFormat(t *testing.T) { 2430 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 2431 rule "terraform_naming_convention" { 2432 enabled = true 2433 format = "mixed_snake_case" 2434 2435 variable { 2436 format = "snake_case" 2437 } 2438 }`) 2439 } 2440 2441 func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideCustom(t *testing.T) { 2442 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2443 rule "terraform_naming_convention" { 2444 enabled = true 2445 format = "mixed_snake_case" 2446 2447 variable { 2448 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2449 } 2450 }`) 2451 } 2452 2453 func Test_TerraformNamingConventionRule_Variable_DefaultCustom_OverrideFormat(t *testing.T) { 2454 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 2455 rule "terraform_naming_convention" { 2456 enabled = true 2457 custom = "^ignored$" 2458 2459 variable { 2460 format = "snake_case" 2461 } 2462 }`) 2463 } 2464 2465 func Test_TerraformNamingConventionRule_Variable_DefaultCustom_OverrideCustom(t *testing.T) { 2466 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2467 rule "terraform_naming_convention" { 2468 enabled = true 2469 custom = "^ignored$" 2470 2471 variable { 2472 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2473 } 2474 }`) 2475 } 2476 2477 func Test_TerraformNamingConventionRule_Variable_DefaultDisabled_OverrideFormat(t *testing.T) { 2478 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", ` 2479 rule "terraform_naming_convention" { 2480 enabled = true 2481 format = "none" 2482 2483 variable { 2484 format = "snake_case" 2485 } 2486 }`) 2487 } 2488 2489 func Test_TerraformNamingConventionRule_Variable_DefaultDisabled_OverrideCustom(t *testing.T) { 2490 testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", ` 2491 rule "terraform_naming_convention" { 2492 enabled = true 2493 format = "none" 2494 2495 variable { 2496 custom = "^[a-z][a-z]*(_[a-z]+)*$" 2497 } 2498 }`) 2499 } 2500 2501 func Test_TerraformNamingConventionRule_Variable_DefaultEmpty_OverrideDisabled(t *testing.T) { 2502 testVariableDisabled(t, `overridden config (format=null)`, ` 2503 rule "terraform_naming_convention" { 2504 enabled = true 2505 2506 variable { 2507 format = "none" 2508 } 2509 }`) 2510 } 2511 2512 func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideDisabled(t *testing.T) { 2513 testVariableDisabled(t, `overridden config (format=null)`, ` 2514 rule "terraform_naming_convention" { 2515 enabled = true 2516 format = "snake_case" 2517 2518 variable { 2519 format = "none" 2520 } 2521 }`) 2522 } 2523 2524 func testVariableSnakeCase(t *testing.T, testType string, formatName string, config string) { 2525 rule := NewTerraformNamingConventionRule() 2526 2527 cases := []struct { 2528 Name string 2529 Content string 2530 Config string 2531 Expected tflint.Issues 2532 }{ 2533 { 2534 Name: fmt.Sprintf("variable: %s - Invalid snake_case with dash", testType), 2535 Content: ` 2536 variable "dash-name" { 2537 description = "invalid" 2538 }`, 2539 Config: config, 2540 Expected: tflint.Issues{ 2541 { 2542 Rule: rule, 2543 Message: fmt.Sprintf("variable name `dash-name` must match the following %s", formatName), 2544 Range: hcl.Range{ 2545 Filename: "tests.tf", 2546 Start: hcl.Pos{Line: 2, Column: 1}, 2547 End: hcl.Pos{Line: 2, Column: 21}, 2548 }, 2549 }, 2550 }, 2551 }, 2552 { 2553 Name: fmt.Sprintf("variable: %s - Invalid snake_case with camelCase", testType), 2554 Content: ` 2555 variable "camelCased" { 2556 description = "invalid" 2557 }`, 2558 Config: config, 2559 Expected: tflint.Issues{ 2560 { 2561 Rule: rule, 2562 Message: fmt.Sprintf("variable name `camelCased` must match the following %s", formatName), 2563 Range: hcl.Range{ 2564 Filename: "tests.tf", 2565 Start: hcl.Pos{Line: 2, Column: 1}, 2566 End: hcl.Pos{Line: 2, Column: 22}, 2567 }, 2568 }, 2569 }, 2570 }, 2571 { 2572 Name: fmt.Sprintf("variable: %s - Invalid snake_case with double underscore", testType), 2573 Content: ` 2574 variable "foo__bar" { 2575 description = "invalid" 2576 }`, 2577 Config: config, 2578 Expected: tflint.Issues{ 2579 { 2580 Rule: rule, 2581 Message: fmt.Sprintf("variable name `foo__bar` must match the following %s", formatName), 2582 Range: hcl.Range{ 2583 Filename: "tests.tf", 2584 Start: hcl.Pos{Line: 2, Column: 1}, 2585 End: hcl.Pos{Line: 2, Column: 20}, 2586 }, 2587 }, 2588 }, 2589 }, 2590 { 2591 Name: fmt.Sprintf("variable: %s - Invalid snake_case with underscore tail", testType), 2592 Content: ` 2593 variable "foo_bar_" { 2594 description = "invalid" 2595 }`, 2596 Config: config, 2597 Expected: tflint.Issues{ 2598 { 2599 Rule: rule, 2600 Message: fmt.Sprintf("variable name `foo_bar_` must match the following %s", formatName), 2601 Range: hcl.Range{ 2602 Filename: "tests.tf", 2603 Start: hcl.Pos{Line: 2, Column: 1}, 2604 End: hcl.Pos{Line: 2, Column: 20}, 2605 }, 2606 }, 2607 }, 2608 }, 2609 { 2610 Name: fmt.Sprintf("variable: %s - Invalid snake_case with Mixed_Snake_Case", testType), 2611 Content: ` 2612 variable "Foo_Bar" { 2613 description = "invalid" 2614 }`, 2615 Config: config, 2616 Expected: tflint.Issues{ 2617 { 2618 Rule: rule, 2619 Message: fmt.Sprintf("variable name `Foo_Bar` must match the following %s", formatName), 2620 Range: hcl.Range{ 2621 Filename: "tests.tf", 2622 Start: hcl.Pos{Line: 2, Column: 1}, 2623 End: hcl.Pos{Line: 2, Column: 19}, 2624 }, 2625 }, 2626 }, 2627 }, 2628 { 2629 Name: fmt.Sprintf("variable: %s - Valid snake_case", testType), 2630 Content: ` 2631 variable "foo_bar" { 2632 description = "valid" 2633 }`, 2634 Config: config, 2635 Expected: tflint.Issues{}, 2636 }, 2637 { 2638 Name: fmt.Sprintf("variable: %s - Valid single word", testType), 2639 Content: ` 2640 variable "foo" { 2641 description = "valid" 2642 }`, 2643 Config: config, 2644 Expected: tflint.Issues{}, 2645 }, 2646 } 2647 2648 for _, tc := range cases { 2649 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2650 2651 if err := rule.Check(runner); err != nil { 2652 t.Fatalf("Unexpected error occurred: %s", err) 2653 } 2654 2655 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2656 } 2657 } 2658 2659 func testVariableMixedSnakeCase(t *testing.T, testType string, config string) { 2660 rule := NewTerraformNamingConventionRule() 2661 2662 cases := []struct { 2663 Name string 2664 Content string 2665 Config string 2666 Expected tflint.Issues 2667 }{ 2668 { 2669 Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with dash", testType), 2670 Content: ` 2671 variable "dash-name" { 2672 description = "invalid" 2673 }`, 2674 Config: config, 2675 Expected: tflint.Issues{ 2676 { 2677 Rule: rule, 2678 Message: "variable name `dash-name` must match the following format: mixed_snake_case", 2679 Range: hcl.Range{ 2680 Filename: "tests.tf", 2681 Start: hcl.Pos{Line: 2, Column: 1}, 2682 End: hcl.Pos{Line: 2, Column: 21}, 2683 }, 2684 }, 2685 }, 2686 }, 2687 { 2688 Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with double underscore", testType), 2689 Content: ` 2690 variable "Foo__Bar" { 2691 description = "invalid" 2692 }`, 2693 Config: config, 2694 Expected: tflint.Issues{ 2695 { 2696 Rule: rule, 2697 Message: "variable name `Foo__Bar` must match the following format: mixed_snake_case", 2698 Range: hcl.Range{ 2699 Filename: "tests.tf", 2700 Start: hcl.Pos{Line: 2, Column: 1}, 2701 End: hcl.Pos{Line: 2, Column: 20}, 2702 }, 2703 }, 2704 }, 2705 }, 2706 { 2707 Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with underscore tail", testType), 2708 Content: ` 2709 variable "Foo_Bar_" { 2710 description = "invalid" 2711 }`, 2712 Config: config, 2713 Expected: tflint.Issues{ 2714 { 2715 Rule: rule, 2716 Message: "variable name `Foo_Bar_` must match the following format: mixed_snake_case", 2717 Range: hcl.Range{ 2718 Filename: "tests.tf", 2719 Start: hcl.Pos{Line: 2, Column: 1}, 2720 End: hcl.Pos{Line: 2, Column: 20}, 2721 }, 2722 }, 2723 }, 2724 }, 2725 { 2726 Name: fmt.Sprintf("variable: %s - Valid snake_case", testType), 2727 Content: ` 2728 variable "foo_bar" { 2729 description = "valid" 2730 }`, 2731 Config: config, 2732 Expected: tflint.Issues{}, 2733 }, 2734 { 2735 Name: fmt.Sprintf("variable: %s - Valid single word", testType), 2736 Content: ` 2737 variable "foo" { 2738 description = "valid" 2739 }`, 2740 Config: config, 2741 Expected: tflint.Issues{}, 2742 }, 2743 { 2744 Name: fmt.Sprintf("variable: %s - Valid Mixed_Snake_Case", testType), 2745 Content: ` 2746 variable "Foo_Bar" { 2747 description = "valid" 2748 }`, 2749 Config: config, 2750 Expected: tflint.Issues{}, 2751 }, 2752 { 2753 Name: fmt.Sprintf("variable: %s - Valid single word with upper characters", testType), 2754 Content: ` 2755 variable "foo" { 2756 description = "valid" 2757 }`, 2758 Config: config, 2759 Expected: tflint.Issues{}, 2760 }, 2761 { 2762 Name: fmt.Sprintf("variable: %s - Valid PascalCase", testType), 2763 Content: ` 2764 variable "PascalCase" { 2765 description = "valid" 2766 }`, 2767 Config: config, 2768 Expected: tflint.Issues{}, 2769 }, 2770 { 2771 Name: fmt.Sprintf("variable: %s - Valid camelCase", testType), 2772 Content: ` 2773 variable "camelCase" { 2774 description = "valid" 2775 }`, 2776 Config: config, 2777 Expected: tflint.Issues{}, 2778 }, 2779 } 2780 2781 for _, tc := range cases { 2782 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2783 2784 if err := rule.Check(runner); err != nil { 2785 t.Fatalf("Unexpected error occurred: %s", err) 2786 } 2787 2788 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2789 } 2790 } 2791 2792 func testVariableDisabled(t *testing.T, testType string, config string) { 2793 rule := NewTerraformNamingConventionRule() 2794 2795 cases := []struct { 2796 Name string 2797 Content string 2798 Config string 2799 Expected tflint.Issues 2800 }{ 2801 { 2802 Name: fmt.Sprintf("variable: %s - Valid mixed_snake_case with dash", testType), 2803 Content: ` 2804 variable "dash-name" { 2805 description = "valid" 2806 }`, 2807 Config: config, 2808 Expected: tflint.Issues{}, 2809 }, 2810 { 2811 Name: fmt.Sprintf("variable: %s - Valid snake_case", testType), 2812 Content: ` 2813 variable "foo_bar" { 2814 description = "valid" 2815 }`, 2816 Config: config, 2817 Expected: tflint.Issues{}, 2818 }, 2819 { 2820 Name: fmt.Sprintf("variable: %s - Valid single word", testType), 2821 Content: ` 2822 variable "foo" { 2823 description = "valid" 2824 }`, 2825 Config: config, 2826 Expected: tflint.Issues{}, 2827 }, 2828 { 2829 Name: fmt.Sprintf("variable: %s - Valid Mixed_Snake_Case", testType), 2830 Content: ` 2831 variable "Foo_Bar" { 2832 description = "valid" 2833 }`, 2834 Config: config, 2835 Expected: tflint.Issues{}, 2836 }, 2837 { 2838 Name: fmt.Sprintf("variable: %s - Valid single word upper characters", testType), 2839 Content: ` 2840 variable "Foo" { 2841 description = "valid" 2842 }`, 2843 Config: config, 2844 Expected: tflint.Issues{}, 2845 }, 2846 { 2847 Name: fmt.Sprintf("variable: %s - Valid PascalCase", testType), 2848 Content: ` 2849 variable "PascalCase" { 2850 description = "valid" 2851 }`, 2852 Config: config, 2853 Expected: tflint.Issues{}, 2854 }, 2855 { 2856 Name: fmt.Sprintf("variable: %s - Valid camelCase", testType), 2857 Content: ` 2858 variable "camelCase" { 2859 description = "valid" 2860 }`, 2861 Config: config, 2862 Expected: tflint.Issues{}, 2863 }, 2864 } 2865 2866 for _, tc := range cases { 2867 runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config)) 2868 2869 if err := rule.Check(runner); err != nil { 2870 t.Fatalf("Unexpected error occurred: %s", err) 2871 } 2872 2873 tflint.AssertIssues(t, tc.Expected, runner.Issues) 2874 } 2875 } 2876 2877 // TODO: Replace with TestRunner 2878 func loadConfigFromNamingConventionTempFile(t *testing.T, content string) *tflint.Config { 2879 if content == "" { 2880 return tflint.EmptyConfig() 2881 } 2882 2883 tmpfile, err := ioutil.TempFile("", "terraform_naming_convention") 2884 if err != nil { 2885 t.Fatal(err) 2886 } 2887 defer os.Remove(tmpfile.Name()) 2888 2889 if _, err := tmpfile.Write([]byte(content)); err != nil { 2890 t.Fatal(err) 2891 } 2892 config, err := tflint.LoadConfig(tmpfile.Name()) 2893 if err != nil { 2894 t.Fatal(err) 2895 } 2896 return config 2897 }