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