github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/command/validate_test.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/zclconf/go-cty/cty"
    13  
    14  	"github.com/cycloidio/terraform/configs/configschema"
    15  	"github.com/cycloidio/terraform/providers"
    16  	"github.com/cycloidio/terraform/terminal"
    17  )
    18  
    19  func setupTest(t *testing.T, fixturepath string, args ...string) (*terminal.TestOutput, int) {
    20  	view, done := testView(t)
    21  	p := testProvider()
    22  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    23  		ResourceTypes: map[string]providers.Schema{
    24  			"test_instance": {
    25  				Block: &configschema.Block{
    26  					Attributes: map[string]*configschema.Attribute{
    27  						"ami": {Type: cty.String, Optional: true},
    28  					},
    29  					BlockTypes: map[string]*configschema.NestedBlock{
    30  						"network_interface": {
    31  							Nesting: configschema.NestingList,
    32  							Block: configschema.Block{
    33  								Attributes: map[string]*configschema.Attribute{
    34  									"device_index": {Type: cty.String, Optional: true},
    35  									"description":  {Type: cty.String, Optional: true},
    36  									"name":         {Type: cty.String, Optional: true},
    37  								},
    38  							},
    39  						},
    40  					},
    41  				},
    42  			},
    43  		},
    44  	}
    45  	c := &ValidateCommand{
    46  		Meta: Meta{
    47  			testingOverrides: metaOverridesForProvider(p),
    48  			View:             view,
    49  		},
    50  	}
    51  
    52  	args = append(args, "-no-color")
    53  	args = append(args, testFixturePath(fixturepath))
    54  
    55  	code := c.Run(args)
    56  	return done(t), code
    57  }
    58  
    59  func TestValidateCommand(t *testing.T) {
    60  	if output, code := setupTest(t, "validate-valid"); code != 0 {
    61  		t.Fatalf("unexpected non-successful exit code %d\n\n%s", code, output.Stderr())
    62  	}
    63  }
    64  
    65  func TestValidateCommandWithTfvarsFile(t *testing.T) {
    66  	// Create a temporary working directory that is empty because this test
    67  	// requires scanning the current working directory by validate command.
    68  	td := tempDir(t)
    69  	testCopyDir(t, testFixturePath("validate-valid/with-tfvars-file"), td)
    70  	defer os.RemoveAll(td)
    71  	defer testChdir(t, td)()
    72  
    73  	view, done := testView(t)
    74  	c := &ValidateCommand{
    75  		Meta: Meta{
    76  			testingOverrides: metaOverridesForProvider(testProvider()),
    77  			View:             view,
    78  		},
    79  	}
    80  
    81  	args := []string{}
    82  	code := c.Run(args)
    83  	output := done(t)
    84  	if code != 0 {
    85  		t.Fatalf("bad %d\n\n%s", code, output.Stderr())
    86  	}
    87  }
    88  
    89  func TestValidateFailingCommand(t *testing.T) {
    90  	if output, code := setupTest(t, "validate-invalid"); code != 1 {
    91  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
    92  	}
    93  }
    94  
    95  func TestValidateFailingCommandMissingQuote(t *testing.T) {
    96  	output, code := setupTest(t, "validate-invalid/missing_quote")
    97  
    98  	if code != 1 {
    99  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   100  	}
   101  	wantError := "Error: Invalid reference"
   102  	if !strings.Contains(output.Stderr(), wantError) {
   103  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   104  	}
   105  }
   106  
   107  func TestValidateFailingCommandMissingVariable(t *testing.T) {
   108  	output, code := setupTest(t, "validate-invalid/missing_var")
   109  	if code != 1 {
   110  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   111  	}
   112  	wantError := "Error: Reference to undeclared input variable"
   113  	if !strings.Contains(output.Stderr(), wantError) {
   114  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   115  	}
   116  }
   117  
   118  func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
   119  	output, code := setupTest(t, "validate-invalid/multiple_providers")
   120  	if code != 1 {
   121  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   122  	}
   123  	wantError := "Error: Duplicate provider configuration"
   124  	if !strings.Contains(output.Stderr(), wantError) {
   125  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   126  	}
   127  }
   128  
   129  func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
   130  	output, code := setupTest(t, "validate-invalid/multiple_modules")
   131  	if code != 1 {
   132  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   133  	}
   134  	wantError := "Error: Duplicate module call"
   135  	if !strings.Contains(output.Stderr(), wantError) {
   136  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   137  	}
   138  }
   139  
   140  func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
   141  	output, code := setupTest(t, "validate-invalid/multiple_resources")
   142  	if code != 1 {
   143  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   144  	}
   145  	wantError := `Error: Duplicate resource "aws_instance" configuration`
   146  	if !strings.Contains(output.Stderr(), wantError) {
   147  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   148  	}
   149  }
   150  
   151  func TestOutputWithoutValueShouldFail(t *testing.T) {
   152  	output, code := setupTest(t, "validate-invalid/outputs")
   153  	if code != 1 {
   154  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   155  	}
   156  	wantError := `The argument "value" is required, but no definition was found.`
   157  	if !strings.Contains(output.Stderr(), wantError) {
   158  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   159  	}
   160  	wantError = `An argument named "values" is not expected here. Did you mean "value"?`
   161  	if !strings.Contains(output.Stderr(), wantError) {
   162  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   163  	}
   164  }
   165  
   166  func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
   167  	output, code := setupTest(t, "validate-invalid/incorrectmodulename")
   168  	if code != 1 {
   169  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   170  	}
   171  
   172  	wantError := `Error: Invalid module instance name`
   173  	if !strings.Contains(output.Stderr(), wantError) {
   174  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   175  	}
   176  	wantError = `Error: Variables not allowed`
   177  	if !strings.Contains(output.Stderr(), wantError) {
   178  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   179  	}
   180  }
   181  
   182  func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
   183  	output, code := setupTest(t, "validate-invalid/interpolation")
   184  	if code != 1 {
   185  		t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
   186  	}
   187  
   188  	wantError := `Error: Variables not allowed`
   189  	if !strings.Contains(output.Stderr(), wantError) {
   190  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   191  	}
   192  	wantError = `A single static variable reference is required`
   193  	if !strings.Contains(output.Stderr(), wantError) {
   194  		t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
   195  	}
   196  }
   197  
   198  func TestMissingDefinedVar(t *testing.T) {
   199  	output, code := setupTest(t, "validate-invalid/missing_defined_var")
   200  	// This is allowed because validate tests only that variables are referenced
   201  	// correctly, not that they all have defined values.
   202  	if code != 0 {
   203  		t.Fatalf("Should have passed: %d\n\n%s", code, output.Stderr())
   204  	}
   205  }
   206  
   207  func TestValidate_json(t *testing.T) {
   208  	tests := []struct {
   209  		path  string
   210  		valid bool
   211  	}{
   212  		{"validate-valid", true},
   213  		{"validate-invalid", false},
   214  		{"validate-invalid/missing_quote", false},
   215  		{"validate-invalid/missing_var", false},
   216  		{"validate-invalid/multiple_providers", false},
   217  		{"validate-invalid/multiple_modules", false},
   218  		{"validate-invalid/multiple_resources", false},
   219  		{"validate-invalid/outputs", false},
   220  		{"validate-invalid/incorrectmodulename", false},
   221  		{"validate-invalid/interpolation", false},
   222  		{"validate-invalid/missing_defined_var", true},
   223  	}
   224  
   225  	for _, tc := range tests {
   226  		t.Run(tc.path, func(t *testing.T) {
   227  			var want, got map[string]interface{}
   228  
   229  			wantFile, err := os.Open(path.Join(testFixturePath(tc.path), "output.json"))
   230  			if err != nil {
   231  				t.Fatalf("failed to open output file: %s", err)
   232  			}
   233  			defer wantFile.Close()
   234  			wantBytes, err := ioutil.ReadAll(wantFile)
   235  			if err != nil {
   236  				t.Fatalf("failed to read output file: %s", err)
   237  			}
   238  			err = json.Unmarshal([]byte(wantBytes), &want)
   239  			if err != nil {
   240  				t.Fatalf("failed to unmarshal expected JSON: %s", err)
   241  			}
   242  
   243  			output, code := setupTest(t, tc.path, "-json")
   244  
   245  			gotString := output.Stdout()
   246  			err = json.Unmarshal([]byte(gotString), &got)
   247  			if err != nil {
   248  				t.Fatalf("failed to unmarshal actual JSON: %s", err)
   249  			}
   250  
   251  			if !cmp.Equal(got, want) {
   252  				t.Errorf("wrong output:\n %v\n", cmp.Diff(got, want))
   253  				t.Errorf("raw output:\n%s\n", gotString)
   254  			}
   255  
   256  			if tc.valid && code != 0 {
   257  				t.Errorf("wrong exit code: want 0, got %d", code)
   258  			} else if !tc.valid && code != 1 {
   259  				t.Errorf("wrong exit code: want 1, got %d", code)
   260  			}
   261  
   262  			if errorOutput := output.Stderr(); errorOutput != "" {
   263  				t.Errorf("unexpected error output:\n%s", errorOutput)
   264  			}
   265  		})
   266  	}
   267  }