github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/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/hashicorp/terraform/internal/configs/configschema" 15 "github.com/hashicorp/terraform/internal/providers" 16 "github.com/hashicorp/terraform/internal/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 := t.TempDir() 69 testCopyDir(t, testFixturePath("validate-valid/with-tfvars-file"), td) 70 defer testChdir(t, td)() 71 72 view, done := testView(t) 73 c := &ValidateCommand{ 74 Meta: Meta{ 75 testingOverrides: metaOverridesForProvider(testProvider()), 76 View: view, 77 }, 78 } 79 80 args := []string{} 81 code := c.Run(args) 82 output := done(t) 83 if code != 0 { 84 t.Fatalf("bad %d\n\n%s", code, output.Stderr()) 85 } 86 } 87 88 func TestValidateFailingCommand(t *testing.T) { 89 if output, code := setupTest(t, "validate-invalid"); code != 1 { 90 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 91 } 92 } 93 94 func TestValidateFailingCommandMissingQuote(t *testing.T) { 95 output, code := setupTest(t, "validate-invalid/missing_quote") 96 97 if code != 1 { 98 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 99 } 100 wantError := "Error: Invalid reference" 101 if !strings.Contains(output.Stderr(), wantError) { 102 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 103 } 104 } 105 106 func TestValidateFailingCommandMissingVariable(t *testing.T) { 107 output, code := setupTest(t, "validate-invalid/missing_var") 108 if code != 1 { 109 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 110 } 111 wantError := "Error: Reference to undeclared input variable" 112 if !strings.Contains(output.Stderr(), wantError) { 113 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 114 } 115 } 116 117 func TestSameProviderMutipleTimesShouldFail(t *testing.T) { 118 output, code := setupTest(t, "validate-invalid/multiple_providers") 119 if code != 1 { 120 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 121 } 122 wantError := "Error: Duplicate provider configuration" 123 if !strings.Contains(output.Stderr(), wantError) { 124 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 125 } 126 } 127 128 func TestSameModuleMultipleTimesShouldFail(t *testing.T) { 129 output, code := setupTest(t, "validate-invalid/multiple_modules") 130 if code != 1 { 131 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 132 } 133 wantError := "Error: Duplicate module call" 134 if !strings.Contains(output.Stderr(), wantError) { 135 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 136 } 137 } 138 139 func TestSameResourceMultipleTimesShouldFail(t *testing.T) { 140 output, code := setupTest(t, "validate-invalid/multiple_resources") 141 if code != 1 { 142 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 143 } 144 wantError := `Error: Duplicate resource "aws_instance" configuration` 145 if !strings.Contains(output.Stderr(), wantError) { 146 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 147 } 148 } 149 150 func TestOutputWithoutValueShouldFail(t *testing.T) { 151 output, code := setupTest(t, "validate-invalid/outputs") 152 if code != 1 { 153 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 154 } 155 wantError := `The argument "value" is required, but no definition was found.` 156 if !strings.Contains(output.Stderr(), wantError) { 157 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 158 } 159 wantError = `An argument named "values" is not expected here. Did you mean "value"?` 160 if !strings.Contains(output.Stderr(), wantError) { 161 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 162 } 163 } 164 165 func TestModuleWithIncorrectNameShouldFail(t *testing.T) { 166 output, code := setupTest(t, "validate-invalid/incorrectmodulename") 167 if code != 1 { 168 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 169 } 170 171 wantError := `Error: Invalid module instance name` 172 if !strings.Contains(output.Stderr(), wantError) { 173 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 174 } 175 wantError = `Error: Variables not allowed` 176 if !strings.Contains(output.Stderr(), wantError) { 177 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 178 } 179 } 180 181 func TestWronglyUsedInterpolationShouldFail(t *testing.T) { 182 output, code := setupTest(t, "validate-invalid/interpolation") 183 if code != 1 { 184 t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr()) 185 } 186 187 wantError := `Error: Variables not allowed` 188 if !strings.Contains(output.Stderr(), wantError) { 189 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 190 } 191 wantError = `A single static variable reference is required` 192 if !strings.Contains(output.Stderr(), wantError) { 193 t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr()) 194 } 195 } 196 197 func TestMissingDefinedVar(t *testing.T) { 198 output, code := setupTest(t, "validate-invalid/missing_defined_var") 199 // This is allowed because validate tests only that variables are referenced 200 // correctly, not that they all have defined values. 201 if code != 0 { 202 t.Fatalf("Should have passed: %d\n\n%s", code, output.Stderr()) 203 } 204 } 205 206 func TestValidate_json(t *testing.T) { 207 tests := []struct { 208 path string 209 valid bool 210 }{ 211 {"validate-valid", true}, 212 {"validate-invalid", false}, 213 {"validate-invalid/missing_quote", false}, 214 {"validate-invalid/missing_var", false}, 215 {"validate-invalid/multiple_providers", false}, 216 {"validate-invalid/multiple_modules", false}, 217 {"validate-invalid/multiple_resources", false}, 218 {"validate-invalid/outputs", false}, 219 {"validate-invalid/incorrectmodulename", false}, 220 {"validate-invalid/interpolation", false}, 221 {"validate-invalid/missing_defined_var", true}, 222 } 223 224 for _, tc := range tests { 225 t.Run(tc.path, func(t *testing.T) { 226 var want, got map[string]interface{} 227 228 wantFile, err := os.Open(path.Join(testFixturePath(tc.path), "output.json")) 229 if err != nil { 230 t.Fatalf("failed to open output file: %s", err) 231 } 232 defer wantFile.Close() 233 wantBytes, err := ioutil.ReadAll(wantFile) 234 if err != nil { 235 t.Fatalf("failed to read output file: %s", err) 236 } 237 err = json.Unmarshal([]byte(wantBytes), &want) 238 if err != nil { 239 t.Fatalf("failed to unmarshal expected JSON: %s", err) 240 } 241 242 output, code := setupTest(t, tc.path, "-json") 243 244 gotString := output.Stdout() 245 err = json.Unmarshal([]byte(gotString), &got) 246 if err != nil { 247 t.Fatalf("failed to unmarshal actual JSON: %s", err) 248 } 249 250 if !cmp.Equal(got, want) { 251 t.Errorf("wrong output:\n %v\n", cmp.Diff(got, want)) 252 t.Errorf("raw output:\n%s\n", gotString) 253 } 254 255 if tc.valid && code != 0 { 256 t.Errorf("wrong exit code: want 0, got %d", code) 257 } else if !tc.valid && code != 1 { 258 t.Errorf("wrong exit code: want 1, got %d", code) 259 } 260 261 if errorOutput := output.Stderr(); errorOutput != "" { 262 t.Errorf("unexpected error output:\n%s", errorOutput) 263 } 264 }) 265 } 266 }