kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 "kubeform.dev/terraform-backend-sdk/configs/configschema" 15 "kubeform.dev/terraform-backend-sdk/providers" 16 "kubeform.dev/terraform-backend-sdk/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 }