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  }