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

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/fatih/color"
    13  	"github.com/golang/mock/gomock"
    14  	hcl "github.com/hashicorp/hcl/v2"
    15  	"github.com/hashicorp/terraform/configs"
    16  	"github.com/hashicorp/terraform/terraform"
    17  	"github.com/terraform-linters/tflint/rules"
    18  	"github.com/terraform-linters/tflint/tflint"
    19  )
    20  
    21  func TestCLIRun__noIssuesFound(t *testing.T) {
    22  	cases := []struct {
    23  		Name    string
    24  		Command string
    25  		LoadErr error
    26  		Status  int
    27  		Stdout  string
    28  		Stderr  string
    29  	}{
    30  		{
    31  			Name:    "print version",
    32  			Command: "./tflint --version",
    33  			Status:  ExitCodeOK,
    34  			Stdout:  fmt.Sprintf("TFLint version %s", tflint.Version),
    35  		},
    36  		{
    37  			Name:    "print help",
    38  			Command: "./tflint --help",
    39  			Status:  ExitCodeOK,
    40  			Stdout:  "Application Options:",
    41  		},
    42  		{
    43  			Name:    "no options",
    44  			Command: "./tflint",
    45  			Status:  ExitCodeOK,
    46  			Stdout:  "",
    47  		},
    48  		{
    49  			Name:    "specify format",
    50  			Command: "./tflint --format json",
    51  			Status:  ExitCodeOK,
    52  			Stdout:  "[]",
    53  		},
    54  		{
    55  			Name:    "`--force` option",
    56  			Command: "./tflint --force",
    57  			Status:  ExitCodeOK,
    58  			Stdout:  "",
    59  		},
    60  		{
    61  			Name:    "`--only` option",
    62  			Command: "./tflint --only aws_instance_invalid_type",
    63  			Status:  ExitCodeOK,
    64  			Stdout:  "",
    65  		},
    66  		{
    67  			Name:    "loading errors are occurred",
    68  			Command: "./tflint",
    69  			LoadErr: errors.New("Load error occurred"),
    70  			Status:  ExitCodeError,
    71  			Stderr:  "Load error occurred",
    72  		},
    73  		{
    74  			Name:    "removed `debug` options",
    75  			Command: "./tflint --debug",
    76  			Status:  ExitCodeError,
    77  			Stderr:  "`debug` option was removed in v0.8.0. Please set `TFLINT_LOG` environment variables instead",
    78  		},
    79  		{
    80  			Name:    "removed `fast` option",
    81  			Command: "./tflint --fast",
    82  			Status:  ExitCodeError,
    83  			Stderr:  "`fast` option was removed in v0.9.0. The `aws_instance_invalid_ami` rule is already fast enough",
    84  		},
    85  		{
    86  			Name:    "removed `--error-with-issues` option",
    87  			Command: "./tflint --error-with-issues",
    88  			Status:  ExitCodeError,
    89  			Stderr:  "`error-with-issues` option was removed in v0.9.0. The behavior is now default",
    90  		},
    91  		{
    92  			Name:    "removed `--quiet` option",
    93  			Command: "./tflint --quiet",
    94  			Status:  ExitCodeError,
    95  			Stderr:  "`quiet` option was removed in v0.11.0. The behavior is now default",
    96  		},
    97  		{
    98  			Name:    "removed `--ignore-rule` option",
    99  			Command: "./tflint --ignore-rule aws_instance_invalid_type",
   100  			Status:  ExitCodeError,
   101  			Stderr:  "`ignore-rule` option was removed in v0.12.0. Please use `--disable-rule` instead",
   102  		},
   103  		{
   104  			Name:    "invalid options",
   105  			Command: "./tflint --unknown",
   106  			Status:  ExitCodeError,
   107  			Stderr:  "`unknown` is unknown option. Please run `tflint --help`",
   108  		},
   109  		{
   110  			Name:    "invalid format",
   111  			Command: "./tflint --format awesome",
   112  			Status:  ExitCodeError,
   113  			Stderr:  "Invalid value `awesome' for option",
   114  		},
   115  		{
   116  			Name:    "invalid rule name",
   117  			Command: "./tflint --enable-rule nosuchrule",
   118  			Status:  ExitCodeError,
   119  			Stderr:  "Rule not found: nosuchrule",
   120  		},
   121  		{
   122  			Name:    "enable rule which has required configuration",
   123  			Command: "./tflint --enable-rule aws_resource_missing_tags",
   124  			Status:  ExitCodeError,
   125  			Stderr:  "This rule cannot be enabled with the `--enable-rule` option because it lacks the required configuration",
   126  		},
   127  	}
   128  
   129  	ctrl := gomock.NewController(t)
   130  	defer ctrl.Finish()
   131  
   132  	for _, tc := range cases {
   133  		outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
   134  		cli := &CLI{
   135  			outStream: outStream,
   136  			errStream: errStream,
   137  			testMode:  true,
   138  		}
   139  
   140  		loader := tflint.NewMockAbstractLoader(ctrl)
   141  		loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), tc.LoadErr).AnyTimes()
   142  		loader.EXPECT().Files().Return(map[string]*hcl.File{}, tc.LoadErr).AnyTimes()
   143  		loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, tc.LoadErr).AnyTimes()
   144  		loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, tc.LoadErr).AnyTimes()
   145  		loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes()
   146  		cli.loader = loader
   147  
   148  		status := cli.Run(strings.Split(tc.Command, " "))
   149  
   150  		if status != tc.Status {
   151  			t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status)
   152  		}
   153  		if !strings.Contains(outStream.String(), tc.Stdout) {
   154  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String())
   155  		}
   156  		if tc.Stdout == "" && outStream.String() != "" {
   157  			t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String())
   158  		}
   159  		if !strings.Contains(errStream.String(), tc.Stderr) {
   160  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String())
   161  		}
   162  		if tc.Stderr == "" && errStream.String() != "" {
   163  			t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String())
   164  		}
   165  	}
   166  }
   167  
   168  type testRule struct {
   169  	dir string
   170  }
   171  type errorRule struct{}
   172  
   173  func (r *testRule) Name() string {
   174  	return "test_rule"
   175  }
   176  func (r *errorRule) Name() string {
   177  	return "error_rule"
   178  }
   179  
   180  func (r *testRule) Enabled() bool {
   181  	return true
   182  }
   183  func (r *errorRule) Enabled() bool {
   184  	return true
   185  }
   186  
   187  func (r *testRule) Severity() string {
   188  	return tflint.ERROR
   189  }
   190  func (r *errorRule) Severity() string {
   191  	return tflint.ERROR
   192  }
   193  
   194  func (r *testRule) Link() string {
   195  	return ""
   196  }
   197  func (r *errorRule) Link() string {
   198  	return ""
   199  }
   200  
   201  func (r *testRule) Check(runner *tflint.Runner) error {
   202  	filename := "test.tf"
   203  	if r.dir != "" {
   204  		filename = filepath.Join(r.dir, filename)
   205  	}
   206  
   207  	runner.EmitIssue(
   208  		r,
   209  		"This is test error",
   210  		hcl.Range{
   211  			Filename: filename,
   212  			Start:    hcl.Pos{Line: 1},
   213  		},
   214  	)
   215  	return nil
   216  }
   217  func (r *errorRule) Check(runner *tflint.Runner) error {
   218  	return errors.New("Check failed")
   219  }
   220  
   221  func TestCLIRun__issuesFound(t *testing.T) {
   222  	cases := []struct {
   223  		Name    string
   224  		Command string
   225  		Rule    rules.Rule
   226  		Status  int
   227  		Stdout  string
   228  		Stderr  string
   229  	}{
   230  		{
   231  			Name:    "issues found",
   232  			Command: "./tflint",
   233  			Rule:    &testRule{},
   234  			Status:  ExitCodeIssuesFound,
   235  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   236  		},
   237  		{
   238  			Name:    "`--force` option",
   239  			Command: "./tflint --force",
   240  			Rule:    &testRule{},
   241  			Status:  ExitCodeOK,
   242  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   243  		},
   244  		{
   245  			Name:    "`--no-color` option",
   246  			Command: "./tflint --no-color",
   247  			Rule:    &testRule{},
   248  			Status:  ExitCodeIssuesFound,
   249  			Stdout:  "This is test error (test_rule)",
   250  		},
   251  		{
   252  			Name:    "checking errors are occurred",
   253  			Command: "./tflint",
   254  			Rule:    &errorRule{},
   255  			Status:  ExitCodeError,
   256  			Stderr:  "Check failed",
   257  		},
   258  	}
   259  
   260  	ctrl := gomock.NewController(t)
   261  	originalRules := rules.DefaultRules
   262  	defer func() {
   263  		rules.DefaultRules = originalRules
   264  		ctrl.Finish()
   265  	}()
   266  
   267  	for _, tc := range cases {
   268  		// Mock rules
   269  		rules.DefaultRules = []rules.Rule{tc.Rule}
   270  
   271  		outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
   272  		cli := &CLI{
   273  			outStream: outStream,
   274  			errStream: errStream,
   275  			testMode:  true,
   276  		}
   277  
   278  		loader := tflint.NewMockAbstractLoader(ctrl)
   279  		loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), nil).AnyTimes()
   280  		loader.EXPECT().Files().Return(map[string]*hcl.File{}, nil).AnyTimes()
   281  		loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, nil).AnyTimes()
   282  		loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes()
   283  		loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes()
   284  		cli.loader = loader
   285  
   286  		status := cli.Run(strings.Split(tc.Command, " "))
   287  
   288  		if status != tc.Status {
   289  			t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status)
   290  		}
   291  		if !strings.Contains(outStream.String(), tc.Stdout) {
   292  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String())
   293  		}
   294  		if tc.Stdout == "" && outStream.String() != "" {
   295  			t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String())
   296  		}
   297  		if !strings.Contains(errStream.String(), tc.Stderr) {
   298  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String())
   299  		}
   300  		if tc.Stderr == "" && errStream.String() != "" {
   301  			t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String())
   302  		}
   303  	}
   304  }
   305  
   306  func TestCLIRun__withArguments(t *testing.T) {
   307  	cases := []struct {
   308  		Name    string
   309  		Command string
   310  		Dir     string
   311  		Status  int
   312  		Stdout  string
   313  		Stderr  string
   314  	}{
   315  		{
   316  			Name:    "no arguments",
   317  			Command: "./tflint",
   318  			Dir:     ".",
   319  			Status:  ExitCodeIssuesFound,
   320  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   321  		},
   322  		{
   323  			Name:    "files arguments",
   324  			Command: "./tflint template.tf",
   325  			Dir:     ".",
   326  			Status:  ExitCodeOK,
   327  			Stdout:  "",
   328  		},
   329  		{
   330  			Name:    "file not found",
   331  			Command: "./tflint not_found.tf",
   332  			Dir:     ".",
   333  			Status:  ExitCodeError,
   334  			Stderr:  "Failed to load `not_found.tf`: File not found",
   335  		},
   336  		{
   337  			Name:    "not Terraform configuration",
   338  			Command: "./tflint README",
   339  			Dir:     ".",
   340  			Status:  ExitCodeError,
   341  			Stderr:  "Failed to load `README`: File is not a target of Terraform",
   342  		},
   343  		{
   344  			Name:    "multiple files",
   345  			Command: "./tflint template.tf test.tf",
   346  			Dir:     ".",
   347  			Status:  ExitCodeIssuesFound,
   348  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   349  		},
   350  		{
   351  			Name:    "directory argument",
   352  			Command: "./tflint example",
   353  			Dir:     "example",
   354  			Status:  ExitCodeIssuesFound,
   355  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   356  		},
   357  		{
   358  			Name:    "file under the directory",
   359  			Command: fmt.Sprintf("./tflint %s", filepath.Join("example", "test.tf")),
   360  			Dir:     "example",
   361  			Status:  ExitCodeIssuesFound,
   362  			Stdout:  fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")),
   363  		},
   364  		{
   365  			Name:    "multiple directories",
   366  			Command: "./tflint example ./",
   367  			Dir:     "example",
   368  			Status:  ExitCodeError,
   369  			Stderr:  "Failed to load `example`: Multiple arguments are not allowed when passing a directory",
   370  		},
   371  		{
   372  			Name:    "file and directory",
   373  			Command: "./tflint template.tf example",
   374  			Dir:     "example",
   375  			Status:  ExitCodeError,
   376  			Stderr:  "Failed to load `example`: Multiple arguments are not allowed when passing a directory",
   377  		},
   378  		{
   379  			Name:    "multiple files in different directories",
   380  			Command: fmt.Sprintf("./tflint test.tf %s", filepath.Join("example", "test.tf")),
   381  			Dir:     "example",
   382  			Status:  ExitCodeError,
   383  			Stderr:  fmt.Sprintf("Failed to load `%s`: Multiple files in different directories are not allowed", filepath.Join("example", "test.tf")),
   384  		},
   385  	}
   386  
   387  	currentDir, err := os.Getwd()
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  	err = os.Chdir(filepath.Join(currentDir, "test-fixtures", "arguments"))
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  
   396  	ctrl := gomock.NewController(t)
   397  	originalRules := rules.DefaultRules
   398  
   399  	defer func() {
   400  		os.Chdir(currentDir)
   401  		rules.DefaultRules = originalRules
   402  		ctrl.Finish()
   403  	}()
   404  
   405  	for _, tc := range cases {
   406  		// Mock rules
   407  		rules.DefaultRules = []rules.Rule{&testRule{dir: tc.Dir}}
   408  
   409  		outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
   410  		cli := &CLI{
   411  			outStream: outStream,
   412  			errStream: errStream,
   413  			testMode:  true,
   414  		}
   415  
   416  		loader := tflint.NewMockAbstractLoader(ctrl)
   417  		loader.EXPECT().LoadConfig(tc.Dir).Return(configs.NewEmptyConfig(), nil).AnyTimes()
   418  		loader.EXPECT().Files().Return(map[string]*hcl.File{}, nil).AnyTimes()
   419  		loader.EXPECT().LoadAnnotations(tc.Dir).Return(map[string]tflint.Annotations{}, nil).AnyTimes()
   420  		loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes()
   421  		loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes()
   422  		cli.loader = loader
   423  
   424  		status := cli.Run(strings.Split(tc.Command, " "))
   425  
   426  		if status != tc.Status {
   427  			t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status)
   428  		}
   429  		if !strings.Contains(outStream.String(), tc.Stdout) {
   430  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String())
   431  		}
   432  		if tc.Stdout == "" && outStream.String() != "" {
   433  			t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String())
   434  		}
   435  		if !strings.Contains(errStream.String(), tc.Stderr) {
   436  			t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String())
   437  		}
   438  		if tc.Stderr == "" && errStream.String() != "" {
   439  			t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String())
   440  		}
   441  	}
   442  }