github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/tflint/config_test.go (about)

     1  package tflint
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	hcl "github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/hclsyntax"
    14  	tfplugin "github.com/terraform-linters/tflint-plugin-sdk/tflint"
    15  	"github.com/terraform-linters/tflint/client"
    16  )
    17  
    18  func Test_LoadConfig(t *testing.T) {
    19  	currentDir, err := os.Getwd()
    20  	if err != nil {
    21  		t.Fatal(err)
    22  	}
    23  
    24  	cases := []struct {
    25  		Name     string
    26  		File     string
    27  		Fallback string
    28  		Expected *Config
    29  	}{
    30  		{
    31  			Name: "load file",
    32  			File: filepath.Join(currentDir, "test-fixtures", "config", "config.hcl"),
    33  			Expected: &Config{
    34  				Module:    true,
    35  				DeepCheck: true,
    36  				Force:     true,
    37  				AwsCredentials: client.AwsCredentials{
    38  					AccessKey: "AWS_ACCESS_KEY",
    39  					SecretKey: "AWS_SECRET_KEY",
    40  					Region:    "us-east-1",
    41  					Profile:   "production",
    42  					CredsFile: "~/.aws/myapp",
    43  				},
    44  				IgnoreModules: map[string]bool{
    45  					"github.com/terraform-linters/example-module": true,
    46  				},
    47  				Varfiles:          []string{"example1.tfvars", "example2.tfvars"},
    48  				Variables:         []string{"foo=bar", "bar=['foo']"},
    49  				DisabledByDefault: false,
    50  				Rules: map[string]*RuleConfig{
    51  					"aws_instance_invalid_type": {
    52  						Name:    "aws_instance_invalid_type",
    53  						Enabled: false,
    54  					},
    55  					"aws_instance_previous_type": {
    56  						Name:    "aws_instance_previous_type",
    57  						Enabled: false,
    58  					},
    59  				},
    60  				Plugins: map[string]*PluginConfig{
    61  					"foo": {
    62  						Name:    "foo",
    63  						Enabled: true,
    64  					},
    65  					"bar": {
    66  						Name:    "bar",
    67  						Enabled: false,
    68  					},
    69  				},
    70  			},
    71  		},
    72  		{
    73  			Name:     "empty file",
    74  			File:     filepath.Join(currentDir, "test-fixtures", "config", "empty.hcl"),
    75  			Expected: EmptyConfig(),
    76  		},
    77  		{
    78  			Name:     "fallback",
    79  			File:     filepath.Join(currentDir, "test-fixtures", "config", "not_found.hcl"),
    80  			Fallback: filepath.Join(currentDir, "test-fixtures", "config", "fallback.hcl"),
    81  			Expected: &Config{
    82  				Module:    false,
    83  				DeepCheck: true,
    84  				Force:     true,
    85  				AwsCredentials: client.AwsCredentials{
    86  					AccessKey: "AWS_ACCESS_KEY",
    87  					SecretKey: "AWS_SECRET_KEY",
    88  					Region:    "us-east-1",
    89  				},
    90  				IgnoreModules:     map[string]bool{},
    91  				Varfiles:          []string{},
    92  				Variables:         []string{},
    93  				DisabledByDefault: true,
    94  				Rules:             map[string]*RuleConfig{},
    95  				Plugins:           map[string]*PluginConfig{},
    96  			},
    97  		},
    98  		{
    99  			Name:     "fallback file not found",
   100  			File:     filepath.Join(currentDir, "test-fixtures", "config", "not_found.hcl"),
   101  			Fallback: filepath.Join(currentDir, "test-fixtures", "config", "fallback_not_found.hcl"),
   102  			Expected: EmptyConfig(),
   103  		},
   104  	}
   105  
   106  	for _, tc := range cases {
   107  		originalDefault := defaultConfigFile
   108  		defaultConfigFile = filepath.Join(currentDir, "test-fixtures", "config", "not_found.hcl")
   109  		originalFallback := fallbackConfigFile
   110  		fallbackConfigFile = tc.Fallback
   111  
   112  		ret, err := LoadConfig(tc.File)
   113  		if err != nil {
   114  			t.Fatalf("Failed `%s` test: Unexpected error occurred: %s", tc.Name, err)
   115  		}
   116  
   117  		opts := []cmp.Option{
   118  			cmpopts.IgnoreFields(RuleConfig{}, "Body"),
   119  		}
   120  		if !cmp.Equal(tc.Expected, ret, opts...) {
   121  			t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(tc.Expected, ret))
   122  		}
   123  
   124  		defaultConfigFile = originalDefault
   125  		fallbackConfigFile = originalFallback
   126  	}
   127  }
   128  
   129  func Test_LoadConfig_error(t *testing.T) {
   130  	currentDir, err := os.Getwd()
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	cases := []struct {
   136  		Name     string
   137  		File     string
   138  		Expected string
   139  	}{
   140  		{
   141  			Name: "file not found",
   142  			File: filepath.Join(currentDir, "test-fixtures", "config", "not_found.hcl"),
   143  			Expected: fmt.Sprintf(
   144  				"`%s` is not found",
   145  				filepath.Join(currentDir, "test-fixtures", "config", "not_found.hcl"),
   146  			),
   147  		},
   148  		{
   149  			Name: "syntax error",
   150  			File: filepath.Join(currentDir, "test-fixtures", "config", "syntax_error.hcl"),
   151  			Expected: fmt.Sprintf(
   152  				"%s:1,1-2: Invalid character; The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\"., and 1 other diagnostic(s)",
   153  				filepath.Join(currentDir, "test-fixtures", "config", "syntax_error.hcl"),
   154  			),
   155  		},
   156  		{
   157  			Name: "invalid config",
   158  			File: filepath.Join(currentDir, "test-fixtures", "config", "invalid.hcl"),
   159  			Expected: fmt.Sprintf(
   160  				"%s:1,34-42: Extraneous label for rule; Only 1 labels (name) are expected for rule blocks.",
   161  				filepath.Join(currentDir, "test-fixtures", "config", "invalid.hcl"),
   162  			),
   163  		},
   164  		{
   165  			Name:     "terraform_version",
   166  			File:     filepath.Join(currentDir, "test-fixtures", "config", "terraform_version.hcl"),
   167  			Expected: "`terraform_version` was removed in v0.9.0 because the option is no longer used",
   168  		},
   169  		{
   170  			Name:     "ignore_rule",
   171  			File:     filepath.Join(currentDir, "test-fixtures", "config", "ignore_rule.hcl"),
   172  			Expected: "`ignore_rule` was removed in v0.12.0. Please define `rule` block with `enabled = false` instead",
   173  		},
   174  	}
   175  
   176  	for _, tc := range cases {
   177  		_, err := LoadConfig(tc.File)
   178  		if err == nil {
   179  			t.Fatalf("Failed `%s` test: Expected error does not occurred", tc.Name)
   180  		}
   181  
   182  		if err.Error() != tc.Expected {
   183  			t.Fatalf("Failed `%s` test: expected error is `%s`, but get `%s`", tc.Name, tc.Expected, err.Error())
   184  		}
   185  	}
   186  }
   187  
   188  func Test_Merge(t *testing.T) {
   189  	file1, diags := hclsyntax.ParseConfig([]byte(`foo = "bar"`), "test.hcl", hcl.Pos{})
   190  	if diags.HasErrors() {
   191  		t.Fatalf("Failed to parse test config: %s", diags)
   192  	}
   193  	file2, diags := hclsyntax.ParseConfig([]byte(`bar = "baz"`), "test2.hcl", hcl.Pos{})
   194  	if diags.HasErrors() {
   195  		t.Fatalf("Failed to parse test config: %s", diags)
   196  	}
   197  
   198  	cfg := &Config{
   199  		Module:    true,
   200  		DeepCheck: true,
   201  		Force:     true,
   202  		AwsCredentials: client.AwsCredentials{
   203  			AccessKey: "access_key",
   204  			SecretKey: "secret_key",
   205  			Region:    "us-east-1",
   206  		},
   207  		IgnoreModules: map[string]bool{
   208  			"github.com/terraform-linters/example-1": true,
   209  			"github.com/terraform-linters/example-2": false,
   210  		},
   211  		Varfiles:          []string{"example1.tfvars", "example2.tfvars"},
   212  		Variables:         []string{"foo=bar"},
   213  		DisabledByDefault: false,
   214  		Rules: map[string]*RuleConfig{
   215  			"aws_instance_invalid_type": {
   216  				Name:    "aws_instance_invalid_type",
   217  				Enabled: false,
   218  				Body:    file1.Body,
   219  			},
   220  			"aws_instance_invalid_ami": {
   221  				Name:    "aws_instance_invalid_ami",
   222  				Enabled: true,
   223  				Body:    file2.Body,
   224  			},
   225  		},
   226  		Plugins: map[string]*PluginConfig{},
   227  	}
   228  
   229  	cases := []struct {
   230  		Name     string
   231  		Base     *Config
   232  		Other    *Config
   233  		Expected *Config
   234  	}{
   235  		{
   236  			Name:     "empty",
   237  			Base:     EmptyConfig(),
   238  			Other:    EmptyConfig(),
   239  			Expected: EmptyConfig(),
   240  		},
   241  		{
   242  			Name:     "prefer base",
   243  			Base:     cfg,
   244  			Other:    EmptyConfig(),
   245  			Expected: cfg,
   246  		},
   247  		{
   248  			Name:     "prefer other",
   249  			Base:     EmptyConfig(),
   250  			Other:    cfg,
   251  			Expected: cfg,
   252  		},
   253  		{
   254  			Name: "override and merge",
   255  			Base: &Config{
   256  				Module:    true,
   257  				DeepCheck: true,
   258  				Force:     false,
   259  				AwsCredentials: client.AwsCredentials{
   260  					AccessKey: "access_key",
   261  					SecretKey: "secret_key",
   262  					Profile:   "production",
   263  					Region:    "us-east-1",
   264  				},
   265  				IgnoreModules: map[string]bool{
   266  					"github.com/terraform-linters/example-1": true,
   267  					"github.com/terraform-linters/example-2": false,
   268  				},
   269  				Varfiles:          []string{"example1.tfvars", "example2.tfvars"},
   270  				Variables:         []string{"foo=bar"},
   271  				DisabledByDefault: false,
   272  				Rules: map[string]*RuleConfig{
   273  					"aws_instance_invalid_type": {
   274  						Name:    "aws_instance_invalid_type",
   275  						Enabled: false,
   276  						Body:    file1.Body,
   277  					},
   278  					"aws_instance_invalid_ami": {
   279  						Name:    "aws_instance_invalid_ami",
   280  						Enabled: true,
   281  						Body:    file1.Body,
   282  					},
   283  				},
   284  				Plugins: map[string]*PluginConfig{
   285  					"foo": {
   286  						Name:    "foo",
   287  						Enabled: true,
   288  					},
   289  					"bar": {
   290  						Name:    "bar",
   291  						Enabled: false,
   292  					},
   293  				},
   294  			},
   295  			Other: &Config{
   296  				Module:    false,
   297  				DeepCheck: false,
   298  				Force:     true,
   299  				AwsCredentials: client.AwsCredentials{
   300  					AccessKey: "ACCESS_KEY",
   301  					SecretKey: "SECRET_KEY",
   302  					Region:    "ap-northeast-1",
   303  					CredsFile: "~/.aws/myapp",
   304  				},
   305  				IgnoreModules: map[string]bool{
   306  					"github.com/terraform-linters/example-2": true,
   307  					"github.com/terraform-linters/example-3": false,
   308  				},
   309  				Varfiles:          []string{"example3.tfvars"},
   310  				Variables:         []string{"bar=baz"},
   311  				DisabledByDefault: false,
   312  				Rules: map[string]*RuleConfig{
   313  					"aws_instance_invalid_ami": {
   314  						Name:    "aws_instance_invalid_ami",
   315  						Enabled: false,
   316  						Body:    file2.Body,
   317  					},
   318  					"aws_instance_previous_type": {
   319  						Name:    "aws_instance_previous_type",
   320  						Enabled: false,
   321  						Body:    file2.Body,
   322  					},
   323  				},
   324  				Plugins: map[string]*PluginConfig{
   325  					"baz": {
   326  						Name:    "baz",
   327  						Enabled: true,
   328  					},
   329  					"bar": {
   330  						Name:    "bar",
   331  						Enabled: true,
   332  					},
   333  				},
   334  			},
   335  			Expected: &Config{
   336  				Module:    true,
   337  				DeepCheck: true, // DeepCheck will not override
   338  				Force:     true,
   339  				AwsCredentials: client.AwsCredentials{
   340  					AccessKey: "ACCESS_KEY",
   341  					SecretKey: "SECRET_KEY",
   342  					Profile:   "production",
   343  					Region:    "ap-northeast-1",
   344  					CredsFile: "~/.aws/myapp",
   345  				},
   346  				IgnoreModules: map[string]bool{
   347  					"github.com/terraform-linters/example-1": true,
   348  					"github.com/terraform-linters/example-2": true,
   349  					"github.com/terraform-linters/example-3": false,
   350  				},
   351  				Varfiles:          []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"},
   352  				Variables:         []string{"foo=bar", "bar=baz"},
   353  				DisabledByDefault: false,
   354  				Rules: map[string]*RuleConfig{
   355  					"aws_instance_invalid_type": {
   356  						Name:    "aws_instance_invalid_type",
   357  						Enabled: false,
   358  						Body:    file1.Body,
   359  					},
   360  					"aws_instance_invalid_ami": {
   361  						Name:    "aws_instance_invalid_ami",
   362  						Enabled: false,
   363  						Body:    file2.Body,
   364  					},
   365  					"aws_instance_previous_type": {
   366  						Name:    "aws_instance_previous_type",
   367  						Enabled: false,
   368  						Body:    file2.Body,
   369  					},
   370  				},
   371  				Plugins: map[string]*PluginConfig{
   372  					"foo": {
   373  						Name:    "foo",
   374  						Enabled: true,
   375  					},
   376  					"bar": {
   377  						Name:    "bar",
   378  						Enabled: true,
   379  					},
   380  					"baz": {
   381  						Name:    "baz",
   382  						Enabled: true,
   383  					},
   384  				},
   385  			},
   386  		},
   387  		{
   388  			Name: "CLI --only argument and merge",
   389  			Base: &Config{
   390  				Module:    true,
   391  				DeepCheck: true,
   392  				Force:     false,
   393  				AwsCredentials: client.AwsCredentials{
   394  					AccessKey: "access_key",
   395  					SecretKey: "secret_key",
   396  					Profile:   "production",
   397  					Region:    "us-east-1",
   398  				},
   399  				IgnoreModules: map[string]bool{
   400  					"github.com/terraform-linters/example-1": true,
   401  					"github.com/terraform-linters/example-2": false,
   402  				},
   403  				Varfiles:          []string{"example1.tfvars", "example2.tfvars"},
   404  				Variables:         []string{"foo=bar"},
   405  				DisabledByDefault: false,
   406  				Rules: map[string]*RuleConfig{
   407  					"aws_instance_invalid_type": {
   408  						Name:    "aws_instance_invalid_type",
   409  						Enabled: false,
   410  						Body:    file1.Body,
   411  					},
   412  					"aws_instance_invalid_ami": {
   413  						Name:    "aws_instance_invalid_ami",
   414  						Enabled: true,
   415  						Body:    file1.Body,
   416  					},
   417  				},
   418  				Plugins: map[string]*PluginConfig{
   419  					"foo": {
   420  						Name:    "foo",
   421  						Enabled: true,
   422  					},
   423  					"bar": {
   424  						Name:    "bar",
   425  						Enabled: false,
   426  					},
   427  				},
   428  			},
   429  			Other: &Config{
   430  				Module:    false,
   431  				DeepCheck: false,
   432  				Force:     true,
   433  				AwsCredentials: client.AwsCredentials{
   434  					AccessKey: "ACCESS_KEY",
   435  					SecretKey: "SECRET_KEY",
   436  					Region:    "ap-northeast-1",
   437  					CredsFile: "~/.aws/myapp",
   438  				},
   439  				IgnoreModules: map[string]bool{
   440  					"github.com/terraform-linters/example-2": true,
   441  					"github.com/terraform-linters/example-3": false,
   442  				},
   443  				Varfiles:          []string{"example3.tfvars"},
   444  				Variables:         []string{"bar=baz"},
   445  				DisabledByDefault: true,
   446  				Rules: map[string]*RuleConfig{
   447  					"aws_instance_invalid_type": {
   448  						Name:    "aws_instance_invalid_type",
   449  						Enabled: true,
   450  						Body:    hcl.EmptyBody(),
   451  					},
   452  					"aws_instance_previous_type": {
   453  						Name:    "aws_instance_previous_type",
   454  						Enabled: true,
   455  						Body:    hcl.EmptyBody(), // Will not attach, missing config
   456  					},
   457  				},
   458  				Plugins: map[string]*PluginConfig{
   459  					"baz": {
   460  						Name:    "baz",
   461  						Enabled: true,
   462  					},
   463  					"bar": {
   464  						Name:    "bar",
   465  						Enabled: true,
   466  					},
   467  				},
   468  			},
   469  			Expected: &Config{
   470  				Module:    true,
   471  				DeepCheck: true, // DeepCheck will not override
   472  				Force:     true,
   473  				AwsCredentials: client.AwsCredentials{
   474  					AccessKey: "ACCESS_KEY",
   475  					SecretKey: "SECRET_KEY",
   476  					Profile:   "production",
   477  					Region:    "ap-northeast-1",
   478  					CredsFile: "~/.aws/myapp",
   479  				},
   480  				IgnoreModules: map[string]bool{
   481  					"github.com/terraform-linters/example-1": true,
   482  					"github.com/terraform-linters/example-2": true,
   483  					"github.com/terraform-linters/example-3": false,
   484  				},
   485  				Varfiles:          []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"},
   486  				Variables:         []string{"foo=bar", "bar=baz"},
   487  				DisabledByDefault: true,
   488  				Rules: map[string]*RuleConfig{
   489  					"aws_instance_invalid_type": {
   490  						Name:    "aws_instance_invalid_type",
   491  						Enabled: true, // Passing an --only rule from CLI will always enable
   492  						Body:    file1.Body,
   493  					},
   494  					"aws_instance_previous_type": {
   495  						Name:    "aws_instance_previous_type",
   496  						Enabled: true,
   497  						Body:    hcl.EmptyBody(),
   498  					},
   499  				},
   500  				Plugins: map[string]*PluginConfig{
   501  					"foo": {
   502  						Name:    "foo",
   503  						Enabled: true,
   504  					},
   505  					"bar": {
   506  						Name:    "bar",
   507  						Enabled: true,
   508  					},
   509  					"baz": {
   510  						Name:    "baz",
   511  						Enabled: true,
   512  					},
   513  				},
   514  			},
   515  		},
   516  		{
   517  			Name: "merge rule config with CLI-based config",
   518  			Base: &Config{
   519  				Module:            false,
   520  				DeepCheck:         false,
   521  				Force:             false,
   522  				AwsCredentials:    client.AwsCredentials{},
   523  				IgnoreModules:     map[string]bool{},
   524  				Varfiles:          []string{},
   525  				Variables:         []string{},
   526  				DisabledByDefault: false,
   527  				Rules: map[string]*RuleConfig{
   528  					"aws_instance_invalid_type": {
   529  						Name:    "aws_instance_invalid_type",
   530  						Enabled: false,
   531  						Body:    file1.Body,
   532  					},
   533  				},
   534  				Plugins: map[string]*PluginConfig{},
   535  			},
   536  			Other: &Config{
   537  				Module:            false,
   538  				DeepCheck:         false,
   539  				Force:             false,
   540  				AwsCredentials:    client.AwsCredentials{},
   541  				IgnoreModules:     map[string]bool{},
   542  				Varfiles:          []string{},
   543  				Variables:         []string{},
   544  				DisabledByDefault: false,
   545  				Rules: map[string]*RuleConfig{
   546  					"aws_instance_invalid_type": {
   547  						Name:    "aws_instance_invalid_type",
   548  						Enabled: true,
   549  						Body:    hcl.EmptyBody(),
   550  					},
   551  				},
   552  				Plugins: map[string]*PluginConfig{},
   553  			},
   554  			Expected: &Config{
   555  				Module:            false,
   556  				DeepCheck:         false,
   557  				Force:             false,
   558  				AwsCredentials:    client.AwsCredentials{},
   559  				IgnoreModules:     map[string]bool{},
   560  				Varfiles:          []string{},
   561  				Variables:         []string{},
   562  				DisabledByDefault: false,
   563  				Rules: map[string]*RuleConfig{
   564  					"aws_instance_invalid_type": {
   565  						Name:    "aws_instance_invalid_type",
   566  						Enabled: true,       // overridden
   567  						Body:    file1.Body, // keep
   568  					},
   569  				},
   570  				Plugins: map[string]*PluginConfig{},
   571  			},
   572  		},
   573  	}
   574  
   575  	for _, tc := range cases {
   576  		ret := tc.Base.Merge(tc.Other)
   577  
   578  		opts := []cmp.Option{
   579  			cmpopts.IgnoreUnexported(hclsyntax.Body{}),
   580  			cmpopts.IgnoreFields(hclsyntax.Body{}, "Attributes", "Blocks"),
   581  		}
   582  		if !cmp.Equal(tc.Expected, ret, opts...) {
   583  			t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(tc.Expected, ret, opts...))
   584  		}
   585  	}
   586  }
   587  
   588  func Test_ToPluginConfig(t *testing.T) {
   589  	config := &Config{
   590  		Rules: map[string]*RuleConfig{
   591  			"aws_instance_invalid_type": {
   592  				Name:    "aws_instance_invalid_type",
   593  				Enabled: false,
   594  			},
   595  			"aws_instance_invalid_ami": {
   596  				Name:    "aws_instance_invalid_ami",
   597  				Enabled: true,
   598  			},
   599  		},
   600  		DisabledByDefault: true,
   601  	}
   602  
   603  	ret := config.ToPluginConfig()
   604  	expected := &tfplugin.Config{
   605  		Rules: map[string]*tfplugin.RuleConfig{
   606  			"aws_instance_invalid_type": {
   607  				Name:    "aws_instance_invalid_type",
   608  				Enabled: false,
   609  			},
   610  			"aws_instance_invalid_ami": {
   611  				Name:    "aws_instance_invalid_ami",
   612  				Enabled: true,
   613  			},
   614  		},
   615  		DisabledByDefault: true,
   616  	}
   617  	if !cmp.Equal(expected, ret) {
   618  		t.Fatalf("Failed to match: %s", cmp.Diff(expected, ret))
   619  	}
   620  }
   621  
   622  type ruleSetA struct{}
   623  
   624  func (*ruleSetA) RuleSetName() (string, error) {
   625  	return "ruleSetA", nil
   626  }
   627  func (*ruleSetA) RuleSetVersion() (string, error) {
   628  	return "0.1.0", nil
   629  }
   630  func (*ruleSetA) RuleNames() ([]string, error) {
   631  	return []string{"aws_instance_invalid_type"}, nil
   632  }
   633  
   634  type ruleSetB struct{}
   635  
   636  func (*ruleSetB) RuleSetName() (string, error) {
   637  	return "ruleSetB", nil
   638  }
   639  func (*ruleSetB) RuleSetVersion() (string, error) {
   640  	return "0.1.0", nil
   641  }
   642  func (*ruleSetB) RuleNames() ([]string, error) {
   643  	return []string{"aws_instance_invalid_ami"}, nil
   644  }
   645  
   646  func Test_ValidateRules(t *testing.T) {
   647  	config := &Config{
   648  		Rules: map[string]*RuleConfig{
   649  			"aws_instance_invalid_type": {
   650  				Name:    "aws_instance_invalid_type",
   651  				Enabled: false,
   652  			},
   653  			"aws_instance_invalid_ami": {
   654  				Name:    "aws_instance_invalid_ami",
   655  				Enabled: true,
   656  			},
   657  		},
   658  	}
   659  
   660  	cases := []struct {
   661  		Name     string
   662  		Config   *Config
   663  		RuleSets []RuleSet
   664  		Err      error
   665  	}{
   666  		{
   667  			Name:     "valid",
   668  			Config:   config,
   669  			RuleSets: []RuleSet{&ruleSetA{}, &ruleSetB{}},
   670  			Err:      nil,
   671  		},
   672  		{
   673  			Name:     "duplicate",
   674  			Config:   config,
   675  			RuleSets: []RuleSet{&ruleSetA{}, &ruleSetB{}, &ruleSetB{}},
   676  			Err:      errors.New("`aws_instance_invalid_ami` is duplicated in ruleSetB and ruleSetB"),
   677  		},
   678  		{
   679  			Name:     "not found",
   680  			Config:   config,
   681  			RuleSets: []RuleSet{&ruleSetB{}},
   682  			Err:      errors.New("Rule not found: aws_instance_invalid_type"),
   683  		},
   684  		{
   685  			Name: "removed rule",
   686  			Config: &Config{
   687  				Rules: map[string]*RuleConfig{
   688  					"terraform_dash_in_resource_name": {
   689  						Name:    "terraform_dash_in_resource_name",
   690  						Enabled: true,
   691  					},
   692  				},
   693  			},
   694  			RuleSets: []RuleSet{&ruleSetA{}, &ruleSetB{}},
   695  			Err:      errors.New("`terraform_dash_in_resource_name` rule was removed in v0.16.0. Please use `terraform_naming_convention` rule instead"),
   696  		},
   697  	}
   698  
   699  	for _, tc := range cases {
   700  		err := tc.Config.ValidateRules(tc.RuleSets...)
   701  
   702  		if tc.Err == nil {
   703  			if err != nil {
   704  				t.Fatalf("Failed %s: Unexpected error occurred: %s", tc.Name, err)
   705  			}
   706  		} else {
   707  			if err == nil {
   708  				t.Fatalf("Failed %s: Error should have occurred, but didn't", tc.Name)
   709  			}
   710  			if err.Error() != tc.Err.Error() {
   711  				t.Fatalf("Failed %s: error message is not matched: want=%s got=%s", tc.Name, tc.Err, err)
   712  			}
   713  		}
   714  	}
   715  }
   716  
   717  func Test_copy(t *testing.T) {
   718  	cfg := &Config{
   719  		Module:    true,
   720  		DeepCheck: true,
   721  		Force:     true,
   722  		AwsCredentials: client.AwsCredentials{
   723  			AccessKey: "access_key",
   724  			SecretKey: "secret_key",
   725  			Region:    "us-east-1",
   726  		},
   727  		IgnoreModules: map[string]bool{
   728  			"github.com/terraform-linters/example-1": true,
   729  			"github.com/terraform-linters/example-2": false,
   730  		},
   731  		Varfiles:          []string{"example1.tfvars", "example2.tfvars"},
   732  		Variables:         []string{},
   733  		DisabledByDefault: true,
   734  		Rules: map[string]*RuleConfig{
   735  			"aws_instance_invalid_type": {
   736  				Name:    "aws_instance_invalid_type",
   737  				Enabled: false,
   738  			},
   739  			"aws_instance_invalid_ami": {
   740  				Name:    "aws_instance_invalid_ami",
   741  				Enabled: true,
   742  			},
   743  		},
   744  		Plugins: map[string]*PluginConfig{
   745  			"foo": {
   746  				Name:    "foo",
   747  				Enabled: true,
   748  			},
   749  			"bar": {
   750  				Name:    "bar",
   751  				Enabled: false,
   752  			},
   753  		},
   754  	}
   755  
   756  	cases := []struct {
   757  		Name       string
   758  		SideEffect func(*Config)
   759  	}{
   760  		{
   761  			Name: "Module",
   762  			SideEffect: func(c *Config) {
   763  				c.Module = false
   764  			},
   765  		},
   766  		{
   767  			Name: "DeepCheck",
   768  			SideEffect: func(c *Config) {
   769  				c.DeepCheck = false
   770  			},
   771  		},
   772  		{
   773  			Name: "Force",
   774  			SideEffect: func(c *Config) {
   775  				c.Force = false
   776  			},
   777  		},
   778  		{
   779  			Name: "DisabledByDefault",
   780  			SideEffect: func(c *Config) {
   781  				c.DisabledByDefault = false
   782  			},
   783  		},
   784  		{
   785  			Name: "AwsCredentials",
   786  			SideEffect: func(c *Config) {
   787  				c.AwsCredentials = client.AwsCredentials{
   788  					Profile: "production",
   789  					Region:  "us-east-1",
   790  				}
   791  			},
   792  		},
   793  		{
   794  			Name: "IgnoreModules",
   795  			SideEffect: func(c *Config) {
   796  				c.IgnoreModules["github.com/terraform-linters/example-1"] = false
   797  			},
   798  		},
   799  		{
   800  			Name: "Varfiles",
   801  			SideEffect: func(c *Config) {
   802  				c.Varfiles = append(c.Varfiles, "new.tfvars")
   803  			},
   804  		},
   805  		{
   806  			Name: "Variables",
   807  			SideEffect: func(c *Config) {
   808  				c.Variables = append(c.Variables, "baz=foo")
   809  			},
   810  		},
   811  		{
   812  			Name: "Rules",
   813  			SideEffect: func(c *Config) {
   814  				c.Rules["aws_instance_invalid_type"].Enabled = true
   815  			},
   816  		},
   817  		{
   818  			Name: "Plugins",
   819  			SideEffect: func(c *Config) {
   820  				c.Plugins["foo"].Enabled = false
   821  			},
   822  		},
   823  	}
   824  
   825  	for _, tc := range cases {
   826  		ret := cfg.copy()
   827  		if !cmp.Equal(cfg, ret) {
   828  			t.Fatalf("The copied config doesn't match original: Diff=%s", cmp.Diff(cfg, ret))
   829  		}
   830  
   831  		tc.SideEffect(ret)
   832  		if cmp.Equal(cfg, ret) {
   833  			t.Fatalf("The original was changed when updating `%s`", tc.Name)
   834  		}
   835  	}
   836  }