github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/cmd/terradoc/validate_test.go (about) 1 package main_test 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/madlambda/spells/assert" 13 "github.com/mineiros-io/terradoc/internal/validators" 14 "github.com/mineiros-io/terradoc/test" 15 ) 16 17 func TestValidateVariables(t *testing.T) { 18 tests := []struct { 19 desc string 20 doc string 21 variables string 22 wantMissingDocumentation []string 23 wantMissingDefinition []string 24 wantTypeMismatch []validators.TypeMismatchResult 25 wantError bool 26 }{ 27 { 28 desc: "when `variables.tf` and terradoc file have the same variables", 29 doc: "validate/variables/complete.tfdoc.hcl", 30 variables: "validate/variables/complete-variables.tf", 31 }, 32 { 33 desc: "when `variables.tf` has missing variables", 34 doc: "validate/variables/complete.tfdoc.hcl", 35 variables: "validate/variables/missing-variables.tf", 36 wantMissingDocumentation: []string{}, 37 wantMissingDefinition: []string{"beer"}, 38 wantError: true, 39 }, 40 { 41 desc: "when terradoc file has missing variables", 42 doc: "validate/variables/missing-variables.tfdoc.hcl", 43 variables: "validate/variables/complete-variables.tf", 44 wantMissingDocumentation: []string{"beer"}, 45 wantMissingDefinition: []string{}, 46 wantError: true, 47 }, 48 { 49 desc: "when `variables.tf` has type mismatch", 50 doc: "validate/variables/complete.tfdoc.hcl", 51 variables: "validate/variables/type-mismatch.tf", 52 wantMissingDocumentation: []string{}, 53 wantMissingDefinition: []string{}, 54 wantTypeMismatch: []validators.TypeMismatchResult{ 55 { 56 Name: "number", 57 DefinedType: "list(string)", 58 DocumentedType: "number", 59 }, 60 }, 61 wantError: true, 62 }, 63 { 64 desc: "when `variables.tf` has type mismatch and missing variables", 65 doc: "validate/variables/complete.tfdoc.hcl", 66 variables: "validate/variables/type-mismatch-with-missing.tf", 67 wantMissingDocumentation: []string{}, 68 wantMissingDefinition: []string{"beer"}, 69 wantTypeMismatch: []validators.TypeMismatchResult{ 70 { 71 Name: "person", 72 DefinedType: "string", 73 DocumentedType: "object(person)", 74 }, 75 }, 76 wantError: true, 77 }, 78 } 79 80 for _, tt := range tests { 81 t.Run(tt.desc, func(t *testing.T) { 82 doc := test.ReadFixture(t, tt.doc) 83 docFile, err := ioutil.TempFile(t.TempDir(), "terradoc-validate-doc-") 84 assert.NoError(t, err) 85 _, err = docFile.Write(doc) 86 assert.NoError(t, err) 87 88 variables := test.ReadFixture(t, tt.variables) 89 90 // Break variables up into multiple files 91 docFileDir := filepath.Dir(docFile.Name()) 92 93 for _, v := range strings.Split(string(variables[:]), "\n\n") { 94 variablesFile, err := ioutil.TempFile(docFileDir, "terradoc-validate-variables-*.tf") 95 assert.NoError(t, err) 96 _, err = variablesFile.Write([]byte(v)) 97 assert.NoError(t, err) 98 } 99 100 err = os.Chdir(docFileDir) 101 assert.NoError(t, err) 102 103 cmd := exec.Command(terradocBinPath, "validate", docFile.Name(), "-v") 104 105 output, err := cmd.CombinedOutput() 106 107 gotResult := splitOutputMessages(t, output, "variable") 108 109 if tt.wantError { 110 assert.Error(t, err) 111 112 assertHasMissingDocumentation(t, docFile.Name(), gotResult.missingDocumentation, tt.wantMissingDocumentation, "variable") 113 assertHasMissingDefinition(t, gotResult.missingDefinition, tt.wantMissingDefinition, "variable") 114 assertHasTypeMismatch(t, docFile.Name(), tt.wantTypeMismatch, gotResult.typeMismatch, "variable") 115 } else { 116 assert.NoError(t, err) 117 } 118 }) 119 } 120 } 121 122 func TestValidateOutputs(t *testing.T) { 123 tests := []struct { 124 desc string 125 doc string 126 outputs string 127 wantMissingDocumentation []string 128 wantMissingDefinition []string 129 wantTypeMismatch []validators.TypeMismatchResult 130 wantError bool 131 }{ 132 { 133 desc: "when `outputs.tf` and terradoc file have the same outputs", 134 doc: "validate/outputs/complete.tfdoc.hcl", 135 outputs: "validate/outputs/complete-outputs.tf", 136 }, 137 { 138 desc: "when `outputs.tf` has missing outputs", 139 doc: "validate/outputs/complete.tfdoc.hcl", 140 outputs: "validate/outputs/missing-outputs.tf", 141 wantMissingDocumentation: []string{}, 142 wantMissingDefinition: []string{"beer"}, 143 wantError: true, 144 }, 145 { 146 desc: "when terradoc file has missing outputs", 147 doc: "validate/outputs/missing-outputs.tfdoc.hcl", 148 outputs: "validate/outputs/complete-outputs.tf", 149 wantMissingDocumentation: []string{"beer"}, 150 wantMissingDefinition: []string{}, 151 wantError: true, 152 }, 153 } 154 155 for _, tt := range tests { 156 t.Run(tt.desc, func(t *testing.T) { 157 doc := test.ReadFixture(t, tt.doc) 158 docFile, err := ioutil.TempFile(t.TempDir(), "terradoc-validate-doc-") 159 assert.NoError(t, err) 160 _, err = docFile.Write(doc) 161 assert.NoError(t, err) 162 163 outputs := test.ReadFixture(t, tt.outputs) 164 165 // Break outputs up into multiple files 166 docFileDir := filepath.Dir(docFile.Name()) 167 168 for _, v := range strings.Split(string(outputs[:]), "\n\n") { 169 outputsFile, err := ioutil.TempFile(docFileDir, "terradoc-validate-outputs-*.tf") 170 assert.NoError(t, err) 171 _, err = outputsFile.Write([]byte(v)) 172 assert.NoError(t, err) 173 } 174 175 err = os.Chdir(docFileDir) 176 assert.NoError(t, err) 177 178 cmd := exec.Command(terradocBinPath, "validate", docFile.Name(), "-o") 179 180 output, err := cmd.CombinedOutput() 181 182 gotResult := splitOutputMessages(t, output, "output") 183 184 if tt.wantError { 185 assert.Error(t, err) 186 187 assertHasMissingDocumentation(t, docFile.Name(), gotResult.missingDocumentation, tt.wantMissingDocumentation, "output") 188 assertHasMissingDefinition(t, gotResult.missingDefinition, tt.wantMissingDefinition, "output") 189 assertHasTypeMismatch(t, docFile.Name(), tt.wantTypeMismatch, gotResult.typeMismatch, "output") 190 } else { 191 assert.NoError(t, err) 192 } 193 }) 194 } 195 } 196 197 type validationResult struct { 198 missingDocumentation []string 199 missingDefinition []string 200 typeMismatch []string 201 } 202 203 func splitOutputMessages(t *testing.T, output []byte, validationType string) validationResult { 204 result := validationResult{} 205 outputStrings := strings.Split(string(output), "\n") 206 207 for _, oo := range outputStrings { 208 switch { 209 case strings.HasPrefix(oo, fmt.Sprintf("Unknown %s documented:", validationType)): 210 result.missingDefinition = append(result.missingDefinition, oo) 211 case strings.HasPrefix(oo, fmt.Sprintf("Missing %s documentation:", validationType)): 212 result.missingDocumentation = append(result.missingDocumentation, oo) 213 case strings.HasPrefix(oo, fmt.Sprintf("Type mismatch for %s:", validationType)): 214 result.typeMismatch = append(result.typeMismatch, oo) 215 } 216 } 217 218 return result 219 } 220 221 func assertHasMissingDocumentation(t *testing.T, filename string, got, want []string, validationType string) { 222 t.Helper() 223 224 if len(got) != len(want) { 225 t.Errorf("wanted %v but got %v", want, got) 226 } 227 228 for _, wantStr := range want { 229 found := false 230 231 completeWantString := fmt.Sprintf("Missing %s documentation: %q is not documented in %q", validationType, wantStr, filename) 232 233 for _, msg := range got { 234 if msg == completeWantString { 235 found = true 236 237 break 238 } 239 } 240 241 if !found { 242 t.Errorf("wanted %s to have missing documentation for %q but didn't find it", validationType, wantStr) 243 } 244 } 245 } 246 247 func assertHasMissingDefinition(t *testing.T, got, want []string, validationType string) { 248 t.Helper() 249 250 if len(got) != len(want) { 251 t.Errorf("wanted %v but got %v", want, got) 252 } 253 254 for _, wantStr := range want { 255 found := false 256 257 completeWantString := fmt.Sprintf("Unknown %s documented: %q is not defined in any .tf files", validationType, wantStr) 258 for _, msg := range got { 259 if msg == completeWantString { 260 found = true 261 262 break 263 } 264 } 265 266 if !found { 267 t.Errorf("wanted %s to have missing definition %q but didn't find it", validationType, wantStr) 268 } 269 } 270 } 271 272 func assertHasTypeMismatch(t *testing.T, docFilename string, want []validators.TypeMismatchResult, got []string, validationType string) { 273 t.Helper() 274 275 if len(got) != len(want) { 276 t.Errorf("wanted %v but got %v", want, got) 277 } 278 279 for _, tm := range want { 280 found := false 281 282 completeWantString := fmt.Sprintf("Type mismatch for %s: %q is documented as %q in %q but defined as %q in .tf files", validationType, tm.Name, tm.DocumentedType, docFilename, tm.DefinedType) 283 284 for _, msg := range got { 285 if msg == completeWantString { 286 found = true 287 288 break 289 } 290 } 291 292 if !found { 293 t.Errorf("wanted %s to have type mismatch for %q but didn't find it", validationType, tm.Name) 294 } 295 } 296 }